mirror of
https://github.com/LCTT/TranslateProject.git
synced 2026-08-23 04:03:29 +08:00
归档201710
This commit is contained in:
95
published/201710/20160511 LEDE and OpenWrt.md
Normal file
95
published/201710/20160511 LEDE and OpenWrt.md
Normal file
@@ -0,0 +1,95 @@
|
||||
LEDE 和 OpenWrt 分裂之争
|
||||
===================
|
||||
|
||||
对于家用 WiFi 路由器和接入点来说,[OpenWrt][1] 项目可能是最广为人知的 Linux 发行版;在 12 年以前,它产自现在有名的 Linksys WRT54G 路由器的源代码。(2016 年)五月初,当一群 OpenWrt 核心开发者 [宣布][2] 他们将开始着手 OpenWrt 的一个副产品 (或者,可能算一个分支)叫 [Linux 嵌入开发环境][3] (LEDE)时,OpenWrt 用户社区陷入一片巨大的混乱中。为什么产生分裂对公众来说并不明朗,而且 LEDE 宣言惊到了一些其他 OpenWrt 开发者也暗示这团队的内部矛盾。
|
||||
|
||||
LEDE 宣言被 Jo-Philipp Wich 于五月三日发往所有 OpenWrt 开发者列表和新 LEDE 开发者列表。它将 LEDE 描述为“OpenWrt 社区的一次重启” 和 “OpenWrt 项目的一个副产品” ,希望产生一个 “注重透明性、合作和权利分散”的 Linux 嵌入式开发社区。
|
||||
|
||||
给出的重启的原因是 OpenWrt 遭受着长期以来存在且不能从内部解决的问题 —— 换句话说,关于内部处理方式和政策。例如,宣言称,开发者的数目在不断减少,却没有接纳新开发者的方式(而且貌似没有授权委托访问给新开发者的方法)。宣言说到,项目的基础设施不可靠(例如,去年服务器挂掉在这个项目中也引发了相当多的矛盾),但是内部不合和单点错误阻止了修复它。内部和从这个项目到外面世界也存在着“交流、透明度和合作”的普遍缺失。最后,一些技术缺陷被引述:不充分的测试、缺乏常规维护,以及窘迫的稳固性与文档。
|
||||
|
||||
该宣言继续描述 LEDE 重启将怎样解决这些问题。所有交流频道都会打开供公众使用,决策将在项目范围内的投票决出,合并政策将放宽等等。更详细的说明可以在 LEDE 站点的[规则][4]页找到。其他细节中,它说贡献者将只有一个阶级(也就是,没有“核心开发者”这样拥有额外权利的群体),简单的少数服从多数投票作出决定,并且任何被这个项目管理的基础设施必须有三个以上管理员账户。在 LEDE 邮件列表, Hauke Mehrtens [补充][5]到,该项目将会努力把补丁投递到上游项目 —— 这是过去 OpenWrt 被批判的一点,尤其是对 Linux 内核。
|
||||
|
||||
除了 Wich,这个宣言被 OpenWrt 贡献者 John Crispin、 Daniel Golle、 Felix Fietkau、 Mehrtens、 Matthias Schiffer 和 Steven Barth 共同签署,并以给其他有兴趣参与的人访问 LEDE 站点的邀请作为了宣言结尾。
|
||||
|
||||
### 回应和问题
|
||||
|
||||
有人可能会猜想 LEDE 组织者预期他们的宣言会有或积极或消极的反响。毕竟,细读宣言中批判 OpenWrt 项目暗示了 LEDE 阵营发现有一些 OpenWrt 项目成员难以共事(例如,“单点错误” 或 “内部不和”阻止了基础设施的修复)。
|
||||
|
||||
并且,确实,有很多消极回应。OpenWrt 创立者之一 Mike Baker [回应][6] 了一些警告,反驳所有 LEDE 宣言中的结论并称“像‘重启’这样的词语都是含糊不清的,且具有误导性的,而且 LEDE 项目未能揭晓其真实本质。”与此同时,有人关闭了那些在 LEDE 宣言上署名的开发者的 @openwrt.org 邮件入口;当 Fietkau [提出反对][7], Baker [回复][8]账户“暂时停用”是因为“还不确定 LEDE 能不能代表 OpenWrt。” 另一个 OpenWrt 核心成员 Imre Kaloz [写][9]到,他们现在所抱怨的 OpenWrt 的“大多数[破]事就是 LEDE 团队弄出来的”。
|
||||
|
||||
但是大多数 OpenWrt 列表的回应对该宣言表示困惑。邮件列表成员不明确 LEDE 团队是否将对 OpenWrt [继续贡献][10],或导致了这次分裂的架构和内部问题的[确切本质][11]是什么。 Baker 的第一反应是对宣言中引述的那些问题缺乏公开讨论表示难过:“我们意识到当前的 OpenWrt 项目遭受着许多的问题,”但“我们希望有机会去讨论并尝试着解决”它们。 Baker 作出结论:
|
||||
|
||||
> 我们想强调,我们确实希望能够公开的讨论,并解决掉手头事情。我们的目标是与所有能够且希望对 OpenWrt 作出贡献的参与者共事,包括 LEDE 团队。
|
||||
|
||||
除了有关新项目的初心的问题之外,一些邮件列表订阅者提出了 LEDE 是否与 OpenWrt 有相同的使用场景定位,给新项目取一个听起来更一般的名字的疑惑。此外,许多人,像 Roman Yeryomin,对为什么这些问题需要 LEDE 团队的离开(来解决)[表示了疑惑][12],特别是,与此同时,LEDE 团队由大部分活跃核心 OpenWrt 开发者构成。一些列表订阅者,像 Michael Richardson,甚至不清楚[谁还会继续开发][13] OpenWrt。
|
||||
|
||||
### 澄清
|
||||
|
||||
LEDE 团队尝试着深入阐释他们的境况。在 Fietkau 给 Baker 的回复中,他说在 OpenWrt 内部关于有目的地改变的讨论会很快变得“有毒,”因此导致没有进展。而且:
|
||||
|
||||
> 这些讨论的要点在于那些掌握着基础设施关键部分的人精力有限却拒绝他人的加入和帮助,甚至是面对无法及时解决的重要问题时也是这样。
|
||||
|
||||
> 这种像单点错误一样的事已经持续了很多年了,没有任何有意义的进展来解决它。
|
||||
|
||||
Wich 和 Fietkau 都没有明显指出具体的人,虽然在列表的其他人可能会想到这个基础设施和 OpenWrt 的内部决策问题要归咎于某些人。 Daniel Dickinson [陈述][14]到:
|
||||
|
||||
> 我的印象是 Kaloz (至少) 以基础设施为胁来保持控制,并且根本性的问题是 OpenWrt 是*不*民主的,而且忽视那些真正在 OpenWrt 工作的人想要的是什么,无视他们的愿望,因为他/他们把控着要害。
|
||||
|
||||
另一方面, Luka Perkov [指出][15] 很多 OpemWrt 开发者想从 Subversion 转移到 Git,但 Fietkau 却阻止这种变化。
|
||||
|
||||
看起来是 OpenWrt 的管理结构并非如预期般发挥作用,其结果导致个人冲突爆发,而且由于没有完好定义的流程,某些人能够简单的忽视或阻止提议的变化。明显,这不是一个能长期持续的模式。
|
||||
|
||||
五月六日,Crispin 在一个新的帖子中[写给][16] OpenWrt 列表,尝试着重构 LEDE 项目宣言。他说,这并不是意味着“敌对或分裂”行为,只是与结构失衡的 OpenWrt 做个清晰的划分并以新的方式开始。问题在于“不要归咎于一次单独的事件、一个人或者一次口水战”,他说,“我们想与过去自己造成的错误和多次作出的错误管理决定分开”。 Crispin 也承认宣言没有把握好,说 LEDE 团队 “弄糟了发起纲领。”
|
||||
|
||||
Crispin 的邮件似乎没能使 Kaloz 满意,她[坚持认为][17] Crispin(作为发行经理)和 Fietkau(作为领头开发者)可以轻易地在 OpenWrt 内部作出想要的改变。但是讨论的下文后来变得沉寂;之后 LEDE 或者 OpenWrt 哪边会发生什么还有待观察。
|
||||
|
||||
### 目的
|
||||
|
||||
对于那些想要探究 LEDE 所认为有问题的事情的更多细节的 OpenWrt 成员来说,有更多的信息来源可以为这个问题提供线索。在公众宣言之前,LEDE 组织花了几周谈论他们的计划,会议的 IRC 日志现已[发布][18]。特别有趣的是,三月三十日的[会议][19]包含了这个项目目标的细节讨论。
|
||||
|
||||
其中包括一些针对 OpenWrt 的基础设施的抱怨,像项目的 Trac 工单追踪器的缺点。它充斥着不完整的漏洞报告和“我也是”的评论,Wich 说,结果几乎没有贡献者使用它。此外,他们也在 Github 上追踪 bug,人们对这件事感到困惑,这使得工单应该在哪里讨论不明了。
|
||||
|
||||
这些 IRC 讨论也定下了开发流程本身。LEDE 团队想作出些改变,以使用会合并到主干的阶段开发分支为开端,与 OpenWrt 所使用的“直接提交到主干”方式不同。该项目也将提供基于时间的发行版,并通过只发行已被成功测试的二进制模块来鼓励用户测试,由社区而不是核心开发者在实际的硬件上进行测试。
|
||||
|
||||
最后,这些 IRC 讨论也确定了 LEDE 团队的目的不是用它的宣言吓唬 OpenWrt。Crispin 提到 LEDE 首先是“半公开的”并渐渐做得更公开。 Wich 解释说他希望 LEDE 是“中立的、专业的,并打开大门欢迎 OpenWrt 以便将来的合并”。不幸的是,前期发起工作并不是做得很好。
|
||||
|
||||
在一封邮件中, Fietkau 补充到 OpenWrt 核心开发者确实在任务中遇到瓶颈,像补丁复审和基础设施维护这些事情让他们完成不了其他工作,比如配置下载镜像和改良构建系统。在 LEDE 宣言之后短短几天内,他说,团队成功解决了镜像和构建系统任务,而这些已被搁置多年。
|
||||
|
||||
> 我们在 LEDE 所做的事情很多是基于转移到 Github 的去中心化软件包开发经验,并放弃了软件包应如何被维护的许多控制。这样最终有效减少了我们的工作量,而且我们有了很多更活跃的开发者。
|
||||
|
||||
> 我们真的希望为核心开发做一些类似的事,但是基于我们想作出更大改变的经验,我们觉得在 OpenWrt 项目内做不到。
|
||||
|
||||
修复基础设施也将收获其他好处,他说,就比如改进了用于管理签署发布版本的密码的系统。团队正在考虑在某些情况下非上游补丁的规则,像需要补丁的描述和为什么没有发送到上游的解释。他也提到很多留下的 OpenWrt 开发者表示有兴趣加入 LEDE,相关当事人正试图弄清楚他们是否会重新合并该项目。
|
||||
|
||||
有人希望 LEDE 更为扁平的管理模式和更为透明的分工会在困扰 OpenWrt 的方面取得成功。解决最初的宣言中被诟病的沟通方面的问题会是最大的障碍。如果那个过程处理得好,那么,未来 LEDE 和 OpenWrt 可能能够求同存异并协作。否则,之后两个团队可能一起被迫发展到比以前拥有更少资源的方向,这也许不是开发者或用户想看到的。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://lwn.net/Articles/686767/
|
||||
|
||||
作者:[Nathan Willis][a]
|
||||
译者:[XYenChi](https://github.com/XYenChi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://lwn.net/Articles/686767/
|
||||
[1]:https://openwrt.org/
|
||||
[2]:https://lwn.net/Articles/686180/
|
||||
[3]:https://www.lede-project.org/
|
||||
[4]:https://www.lede-project.org/rules.html
|
||||
[5]:http://lists.infradead.org/pipermail/lede-dev/2016-May/000080.html
|
||||
[6]:https://lwn.net/Articles/686988/
|
||||
[7]:https://lwn.net/Articles/686989/
|
||||
[8]:https://lwn.net/Articles/686990/
|
||||
[9]:https://lwn.net/Articles/686991/
|
||||
[10]:https://lwn.net/Articles/686995/
|
||||
[11]:https://lwn.net/Articles/686996/
|
||||
[12]:https://lwn.net/Articles/686992/
|
||||
[13]:https://lwn.net/Articles/686993/
|
||||
[14]:https://lwn.net/Articles/686998/
|
||||
[15]:https://lwn.net/Articles/687001/
|
||||
[16]:https://lwn.net/Articles/687003/
|
||||
[17]:https://lwn.net/Articles/687004/
|
||||
[18]:http://meetings.lede-project.org/lede-adm/2016/?C=M;O=A
|
||||
[19]:http://meetings.lede-project.org/lede-adm/2016/lede-adm.2016-03-30-11.05.log.html
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,56 @@
|
||||
如何像 NASA 顶级程序员一样编程 —— 10 条重要原则
|
||||
===
|
||||
|
||||
[][1]
|
||||
|
||||
> 引言: 你知道 NASA 顶级程序员如何编写关键任务代码么?为了确保代码更清楚、更安全、且更容易理解,NASA 的喷气推进实验室制定了 10 条编码规则。
|
||||
|
||||
NASA 的开发者是编程界最有挑战性的工作之一。他们编写代码并将开发安全的关键任务应用程序作为其主要关注点。
|
||||
|
||||
在这种情形下,遵守一些严格的编码规则是重要的。这些规则覆盖软件开发的多个方面,例如软件应该如何编码、应该使用哪些语言特性等。
|
||||
|
||||
尽管很难就一个好的编码标准达成共识,NASA 的喷气推进实验室(JPL)遵守一个[编码规则][2],其名为“十的次方:开发安全的关键代码的规则”。
|
||||
|
||||
由于 JPL 长期使用 C 语言,这个规则主要是针对于 C 程序语言编写。但是这些规则也可以很容地应用到其它的程序语言。
|
||||
|
||||
该规则由 JPL 的首席科学家 Gerard J. Holzmann 制定,这些严格的编码规则主要是聚焦于安全。
|
||||
|
||||
NASA 的 10 条编写关键任务代码的规则:
|
||||
|
||||
1. 限制所有代码为极为简单的控制流结构 — 不用 `goto` 语句、`setjmp` 或 `longjmp` 结构,不用间接或直接的递归调用。
|
||||
2. 所有循环必须有一个固定的上限值。必须可以被某个检测工具静态证实,该循环不能达到预置的迭代上限值。如果该上限值不能被静态证实,那么可以认为违背该原则。
|
||||
3. 在初始化后不要使用动态内存分配。
|
||||
4. 如果一个语句一行、一个声明一行的标准格式来参考,那么函数的长度不应该比超过一张纸。通常这意味着每个函数的代码行不能超过 60。
|
||||
5. 代码中断言的密度平均低至每个函数 2 个断言。断言被用于检测那些在实际执行中不可能发生的情况。断言必须没有副作用,并应该定义为布尔测试。当一个断言失败时,应该执行一个明确的恢复动作,例如,把错误情况返回给执行该断言失败的函数调用者。对于静态工具来说,任何能被静态工具证实其永远不会失败或永远不能触发的断言违反了该规则(例如,通过增加无用的 `assert(true)` 语句是不可能满足这个规则的)。
|
||||
6. 必须在最小的范围内声明数据对象。
|
||||
7. 非 void 函数的返回值在每次函数调用时都必须检查,且在每个函数内其参数的有效性必须进行检查。
|
||||
8. 预处理器的使用仅限制于包含头文件和简单的宏定义。符号拼接、可变参数列表(省略号)和递归宏调用都是不允许的。所有的宏必须能够扩展为完整的语法单元。条件编译指令的使用通常是晦涩的,但也不总是能够避免。这意味着即使在一个大的软件开发中超过一两个条件编译指令也要有充足的理由,这超出了避免多次包含头文件的标准做法。每次在代码中这样做的时候必须有基于工具的检查器进行标记,并有充足的理由。
|
||||
9. 应该限制指针的使用。特别是不应该有超过一级的解除指针引用。解除指针引用操作不可以隐含在宏定义或类型声明中。还有,不允许使用函数指针。
|
||||
10. 从开发的第一天起,必须在编译器开启最高级别警告选项的条件下对代码进行编译。在此设置之下,代码必须零警告编译通过。代码必须利用源代码静态分析工具每天至少检查一次或更多次,且零警告通过。
|
||||
|
||||
关于这些规则,NASA 是这么评价的:
|
||||
|
||||
> 这些规则就像汽车中的安全带一样,刚开始你可能感到有一点不适,但是一段时间后就会养成习惯,你会无法想象不使用它们的日子。
|
||||
|
||||
此文是否对你有帮助?不要忘了在下面的评论区写下你的反馈。
|
||||
|
||||
---
|
||||
作者简介:
|
||||
|
||||
Adarsh Verma 是 Fossbytes 的共同创始人,他是一个令人尊敬的企业家,他一直对开源、技术突破和完全保持密切关注。可以通过邮件联系他 — [adarsh.verma@fossbytes.com](mailto:adarsh.verma@fossbytes.com)
|
||||
|
||||
------------------
|
||||
|
||||
via: https://fossbytes.com/nasa-coding-programming-rules-critical/
|
||||
|
||||
作者:[Adarsh Verma][a]
|
||||
译者:[penghuster](https://github.com/penghuster)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://fossbytes.com/author/adarsh/
|
||||
[1]:http://fossbytes.com/wp-content/uploads/2016/06/rules-of-coding-nasa.jpg
|
||||
[2]:http://pixelscommander.com/wp-content/uploads/2014/12/P10.pdf
|
||||
[3]:https://fossbytes.com/wp-content/uploads/2016/12/learn-to-code-banner-ad-content-1.png
|
||||
[4]:http://pixelscommander.com/wp-content/uploads/2014/12/P10.pdf
|
||||
@@ -0,0 +1,126 @@
|
||||
Adobe 软件的最佳 Linux 替代品
|
||||
============================================================
|
||||
|
||||

|
||||
|
||||
你是一名正在寻找 Adobe 的替代品的 Linux 用户吗?那你不是一个人。如果你是一个狂热的平面设计师,那么你可能很擅长避开昂贵的 Adobe 产品。不过,对于 Adobe 来说,Linux 用户通常是其支持最不利的。因此,Adobe 的替代品是必须的 —— 但是最好的选择是什么?
|
||||
|
||||
这最终要看具体的 Adobe 程序和你希望完成的事情。幸运的是,由于需求是所有发明之母,有人响应了这些号召。其结果是出现了一系列高效的 Adobe 替代品。
|
||||
|
||||
### Evince (Adobe Acrobat Reader)
|
||||
|
||||

|
||||
|
||||
就像 Adobe Acrobat Reader 一样,[Evince][6] 是一个“支持多种文档格式的文档查看器”。例如,用户可以使用 Evince 作为 PDF 查看器。它还支持各种漫画书格式(cbr、cbz、cb7 和 cbt)。你可以在 Evince 网站上找到[支持格式的完整列表][7]。
|
||||
|
||||
对于 Evince ,Linux 用户不用高估,也不用贬低,它就是个标准的查看器。你可以在需要时前往官方网站进行更新。
|
||||
|
||||
### Pixlr (Adobe Photoshop)
|
||||
|
||||

|
||||
|
||||
关于 [Pixlr][8] 的很棒的一点是这个 Adobe 替代品的各种工具可以在线获得。如果你有一个互联网连接,那么你就有一个强大的图像编辑工作区。
|
||||
|
||||
[Pixlr Editor][9] 是 Photoshop 的一个功能强大的替代品,你可以使用图层和相关效果。它还有一些漂亮的绘图和颜色编辑工具。[Pixlr Express][10] 没有这么多功能,因为它主要用于图像增强、调整颜色和清晰度,并增加一些 Instagram 适用的效果!
|
||||
|
||||
你可以通过 Pixlr 完成的任务简直不可置信,而且完全免费的。
|
||||
|
||||
### Inkscape (Adobe Illustrator)
|
||||
|
||||

|
||||
|
||||
[Inkscape][11]是另一个值得推荐的免费 Adobe 替代品。它主要作为一个“专业的矢量图形编辑器”。除了 Illustrator,Inkscape 也与 Corel Draw、Freehand 和 Xara X 的功能差不多。
|
||||
|
||||
它的矢量设计工具可用于制作 logo 和“高可伸缩性”艺术品。Inkscape 包含绘图、形状和文本工具。图层工具允许你锁定、分组或隐藏单个图层。
|
||||
|
||||
### Pinegrow Web Editor (Adobe Dreamweaver)
|
||||
|
||||

|
||||
|
||||
[Pinegrow Web Editor][12] 是 Dreamweaver 在 Linux 上的绝佳替代品。该程序可让你在桌面上直接制作 HTML 网站。
|
||||
|
||||
不仅是使用代码创建(而且需要稍后预览),Pinegrow 可以提供详细的可视化编辑体验。你可以直接查看和测试你的 HTML 项目,实时了解链接是否正常工作,或者图片是否在它该在的地方。Pinegrow 还附带了 WordPress 主题构建器。
|
||||
|
||||
免费试用 30 天。如果你喜欢,你可以一次性支付 $49 购买。
|
||||
|
||||
### Scribus (Adobe InDesign)
|
||||
|
||||

|
||||
|
||||
[Scribus][13] 可能是最接近 Adobe InDesign 的替代品。根据开发者的说法,你应该[认真考虑使用][14] Scribus,因为它是可靠和免费的。
|
||||
|
||||
实际上,Scribus 不仅仅是一个出色的桌面出版工具,也是一个很好的自出版工具。当你可以自己做高质量的杂志和书籍时,为什么要依靠昂贵的商业软件来创建?Scribus 目前允许设计师使用一个 200 色的调色板,下一个稳定版中[承诺将会加倍颜色数][15]。
|
||||
|
||||
### digiKam (Adobe Lightroom)
|
||||
|
||||

|
||||
|
||||
[digiKam][16] 也许是目前 Linux 用户最好的 Lightroom 替代品。功能包括导入照片、整理图片集、图像增强、创建幻灯片等功能。
|
||||
|
||||
它的时尚设计和先进的功能是真正用心之作。实际上,digiKam 背后的人是摄影师们。不仅如此,他们希望在 Linux 中完成在 Lightroom 能做的任何工作。
|
||||
|
||||
### Webflow (Adobe Muse)
|
||||
|
||||

|
||||
|
||||
[Webflow][17] 是另一个可以证明你无需下载软件而可以完成很多事的网站。这是一个非常方便的 Adobe Muse 替代品,Webflow 是创建高响应式网站设计的理想选择。
|
||||
|
||||
Webflow 的最好的一方面是你不需要自己编写代码。你只需拖放图像并写入文本。Webflow 为你做了所有杂事。你可以从头开始构建网站,也可以使用各种模板。虽然是免费的,但是其高级版本还提供了额外的功能,如能够轻松地导出 HTML 和 CSS 以在其他地方使用。
|
||||
|
||||
### Tupi (Adobe Animate)
|
||||
|
||||

|
||||
|
||||
[Tupi][18] 是 Adobe Animate 的替代品,或者也可以用于那些[不太热衷于 Flash 的人][19]。当然,Tupi 的作者说这并不是与 Flash 竞争。然而,其使用 HTML5 的能力使其成为了理想的替代品。
|
||||
|
||||
在 PC 或平板电脑上绘制 2D 动画。不确定如何开始?使用网站的 [YouTube 教程][20]了解如何制作剪贴画动画以及更多。
|
||||
|
||||
### Black Magic Fusion (Adobe After Effects)
|
||||
|
||||

|
||||
|
||||
[Black Magic Fusion][21] 注定是 Adobe After Effects 的替代者。这个视觉效果软件历经了大约 25 年的开发!Fusion 通常用于在好莱坞电影和电视节目中制造令人印象深刻的效果 —— 这靠的是其丰富而时尚的功能。
|
||||
|
||||
Fusion 通过使用节点,即那些“代表效果、过滤器和其他处理的小图标”工作。将这些节点连接在一起,创建一系列复杂的视觉效果。该程序包括许多功能,如图片修饰、对象跟踪和令人兴奋的 3D 效果。
|
||||
|
||||
你可以选择免费版或者 $995 的 Fusion Studio。为了帮助你决定,[你可以比较][22]免费和高级版的 Fusion 功能。
|
||||
|
||||
### 总结
|
||||
|
||||
如你所见,这些远不止于是 Adobe 替代品。由于开源的缘故,显著提升的替代品的不断地发布。我们很快就会看到一个仅为 Linux 用户创建的完整套件。在此之前,你可以随意选择这些替代品。
|
||||
|
||||
知道这里没有提到的其它有用的 Adobe 替代品吗?在下面的评论区分享软件建议。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.maketecheasier.com/adobe-alternatives-for-linux/
|
||||
|
||||
作者:[Toni Matthews-El][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.maketecheasier.com/author/ttmatthe/
|
||||
[1]:https://www.maketecheasier.com/author/ttmatthe/
|
||||
[2]:https://www.maketecheasier.com/adobe-alternatives-for-linux/#comments
|
||||
[3]:https://www.maketecheasier.com/category/linux-tips/
|
||||
[4]:https://support.google.com/adsense/troubleshooter/1631343
|
||||
[5]:https://support.google.com/adsense/troubleshooter/1631343
|
||||
[6]:https://wiki.gnome.org/Apps/Evince
|
||||
[7]:https://wiki.gnome.org/Apps/Evince/SupportedDocumentFormats
|
||||
[8]:https://pixlr.com/
|
||||
[9]:https://pixlr.com/editor/
|
||||
[10]:https://pixlr.com/express/
|
||||
[11]:https://inkscape.org/en/
|
||||
[12]:http://pinegrow.com/
|
||||
[13]:https://www.scribus.net/
|
||||
[14]:https://www.scribus.net/why-on-earth-should-i-use-scribus-2/
|
||||
[15]:https://www.scribus.net/because-color-matters/
|
||||
[16]:http://digikam.org/
|
||||
[17]:https://webflow.com/
|
||||
[18]:http://www.maefloresta.com/portal/
|
||||
[19]:https://www.maketecheasier.com/sites-moving-away-flash/
|
||||
[20]:https://www.youtube.com/user/maefloresta
|
||||
[21]:https://www.blackmagicdesign.com/
|
||||
[22]:https://www.blackmagicdesign.com/products/fusion/compare
|
||||
@@ -0,0 +1,113 @@
|
||||
回顾 Steam Machines 与 SteamOS
|
||||
====
|
||||
|
||||
去年今日(LCTT 译注:本文发表于 2016 年),在非常符合 Valve 风格的跳票之后,大众迎来了 [Steam Machines 的发布][2]。即使是在 Linux 桌面环境对于游戏的支持大步进步的今天,Steam Machines 作为一个平台依然没有飞跃,而 SteamOS 似乎也止步不前。这些由 Valve 发起的项目究竟怎么了?这些项目为何被发起,又是如何失败的?一些改进又是否曾有机会挽救这些项目的成败?
|
||||
|
||||
### 行业环境
|
||||
|
||||
在 2012 年 Windows 8 发布的时候,微软像 iOS 与 Android 那样,为 Windows 集成了一个应用商店。在微软试图推广对触摸体验友好的界面时,为了更好的提供 “Metro” UI 语言指导下的沉浸式触摸体验,他们同时推出了一系列叫做 “WinRT” 的 API。然而为了能够使用这套 API,应用开发者们必须把应用程序通过 Windows 应用商城发布,并且正如其它应用商城那样,微软从中抽成 30%。对于 Valve 的 CEO,Gabe Newell (G 胖) 而言,这种限制发布平台和抽成行为是让人无法接受的,而且他前瞻地看到了微软利用行业龙头地位来推广 Windows 商店和 Metro 应用对于 Valve 潜在的危险,正如当年微软用 IE 浏览器击垮 Netscape 浏览器一样。
|
||||
|
||||
对于 Valve 来说,运行 Windows 的 PC 的优势在于任何人都可以不受操作系统和硬件方的限制运行各种软件。当像 Windows 这样的专有平台对像 Steam 这样的第三方软件限制越来越严格时,应用开发者们自然会想要寻找一个对任何人都更开放和自由的替代品,他们很自然的会想到 Linux 。Linux 本质上只是一套内核,但你可以轻易地使用 GNU 组件、Gnome 等软件在这套内核上开发出一个操作系统,比如 Ubuntu 就是这么来的。推行 Ubuntu 或者其他 Linux 发行版自然可以为 Valve 提供一个无拘无束的平台,以防止微软或者苹果变成 Valve 作为第三方平台之路上的的敌人,但 Linux 甚至给了 Valve 一个创造新的操作系统平台的机会。
|
||||
|
||||
### 概念化
|
||||
|
||||
如果我们把 Steam Machines 叫做主机的话,Valve 当时似乎认定了主机平台是一个机会。为了迎合用户对于电视主机平台用户界面的审美期待,同时也为了让玩家更好地从稍远的距离上在电视上玩游戏,Valve 为 Steam 推出了 Big Picture 模式。Steam Machines 的核心要点是开放性;比方说所有的软件都被设计成可以脱离 Windows 工作,又比如说 Steam Machines 手柄的 CAD 图纸也被公布出来以便支持玩家二次创作。
|
||||
|
||||
原初计划中,Valve 打算设计一款官方的 Steam Machine 作为旗舰机型。但最终,这些机型只在 2013 年的时候作为原型机给与了部分测试者用于测试。Valve 后来也允许像戴尔这样的 OEM 厂商们制造 Steam Machines,并且也赋予了他们制定价格和配置规格的权利。有一家叫做 “Xi3” 的公司展示了他们设计的 Steam Machine 小型机型,那款机型小到可以放在手掌上,这一新闻创造了围绕 Steam Machines 的更多热烈讨论。最终,Valve 决定不自己设计制造 Steam Machines,而全权交给 OEM 合作厂商们。
|
||||
|
||||
这一过程中还有很多天马行空的创意被列入考量,比如在手柄上加入生物识别技术、眼球追踪以及动作控制等。在这些最初的想法里,陀螺仪被加入了 Steam Controller 手柄,HTC Vive 的手柄也有各种动作追踪仪器;这些想法可能最初都来源于 Steam 手柄的设计过程中。手柄最初还有些更激进的设计,比如在中心放置一块可定制化并且会随着游戏内容变化的触摸屏。但最后的最后,发布会上的手柄偏向保守了许多,但也有诸如双触摸板和内置软件等黑科技。Valve 也考虑过制作面向笔记本类型硬件的 Steam Machines 和 SteamOS。这个企划最终没有任何成果,但也许 “Smach Z” 手持游戏机会是发展的方向之一。
|
||||
|
||||
在 [2013 年九月][3],Valve 对外界宣布了 Steam Machines 和 SteamOS, 并且预告会在 2014 年中发布。前述的 300 台原型机在当年 12 月分发给了测试者们,随后次年 1 月,又分发给了开发者们 2000 台原型机。SteamOS 也在那段时间分发给有 Linux 经验的测试者们试用。根据当时的测试反馈,Valve 最终决定把产品发布延期到 2015 年 11 月。
|
||||
|
||||
SteamOS 的延期跳票给合作伙伴带来了问题;戴尔的 Steam Machine 由于早发售了一年,结果不得不改为搭配了额外软件、甚至运行着 Windows 操作系统的 Alienware Alpha。
|
||||
|
||||
### 正式发布
|
||||
|
||||
在最终的正式发布会上,Valve 和 OEM 合作商们发布了 Steam Machines,同时 Valve 还推出了 Steam Controller 手柄和 Steam Link 串流游戏设备。Valve 也在线下零售行业比如 GameStop 里开辟了货架空间。在发布会前,有几家 OEM 合作商退出了与 Valve 的合作;比如 Origin PC 和 Falcon Northwest 这两家高端精品主机设计商。他们宣称 Steam 生态的性能问题和一些限制迫使他们决定弃用 SteamOS。
|
||||
|
||||
Steam Machines 在发布后收到了褒贬不一的评价。另一方面 Steam Link 则普遍受到好评,很多人表示愿意在客厅电视旁为他们已有的 PC 系统购买 Steam Link, 而不是购置一台全新的 Steam Machine。Steam Controller 手柄则受到其丰富功能伴随而来的陡峭学习曲线影响,评价一败涂地。然而针对 Steam Machines 的批评则是最猛烈的。诸如 LinusTechTips 这样的评测团体 (LCTT 译注:YouTube 硬件界老大,个人也经常看他们节目)注意到了主机的明显的不足,其中甚至不乏性能为题。很多厂商的 Machines 都被批评为性价比极低,特别是经过和玩家们自己组装的同配置机器或者电视主机做对比之后。SteamOS 而被批评为兼容性有问题,Bug 太多,以及性能不及 Windows。在所有 Machines 里,戴尔的 Alienware Alpha 被评价为最有意思的一款,主要是由于品牌价值和机型外观极小的缘故。
|
||||
|
||||
通过把 Debian Linux 操作系统作为开发基础,Valve 得以为 SteamOS 平台找到很多原本就存在与 Steam 平台上的 Linux 兼容游戏来作为“首发游戏”。所以起初大家认为在“首发游戏”上 Steam Machines 对比其他新发布的主机优势明显。然而,很多宣称会在新平台上发布的游戏要么跳票要么被中断了。Rocket League 和 Mad Max 在宣布支持新平台整整一年后才真正发布,而《巫师 3》和《蝙蝠侠:阿克汉姆骑士》甚至从来没有发布在新平台上。就《巫师 3》的情况而言,他们的开发者 CD Projekt Red 拒绝承认他们曾经说过要支持新平台;然而他们的游戏曾在宣布支持 Linux 和 SteamOS 的游戏列表里赫然醒目。雪上加霜的是,很多 AAA 级的大作甚至没宣布移植,虽然最近这种情况稍有所好转了。
|
||||
|
||||
### 被忽视的
|
||||
|
||||
在 Stame Machines 发售后,Valve 的开发者们很快转移到了其他项目的工作中去了。在当时,VR 项目最为内部所重视,6 月份的时候大约有 1/3 的员工都在相关项目上工作。Valve 把 VR 视为亟待开发的一片领域,而他们的 Steam 则应该作为分发 VR 内容的生态环境。通过与 HTC 合作生产,Valve 设计并制造出了他们自己的 VR 头戴和手柄,并计划在将来更新换代。然而与此同时,Linux 和 Steam Machines 都渐渐淡出了视野。SteamVR 甚至直到最近才刚刚支持 Linux (其实还没对普通消费者开放使用,只在 SteamDevDays 上展示过对 Linux 的支持),而这一点则让我们怀疑 Valve 在 Stame Machines 和 Linux 的开发上是否下定了足够的决心。
|
||||
|
||||
SteamOS 自发布以来几乎止步不前。SteamOS 2.0 作为上一个大版本号更新,几乎只是同步了 Debian 上游的变化,而且还需要用户重新安装整个系统,而之后的小补丁也只是在做些上游更新的配合。当 Valve 在其他事关性能和用户体验的项目(例如 Mesa)上进步匪浅的时候,针对 Steam Machines 的相关项目则少有顾及。
|
||||
|
||||
很多原本应有的功能都从未完成。Steam 的内置功能,例如聊天和直播,都依然处于较弱的状态,而且这种落后会影响所有平台上的 Steam 用户体验。更具体来说,Steam 没有像其他主流主机平台一样把诸如 Netflix、Twitch 和 Spotify 之类的服务集成到客户端里,而通过 Steam 内置的浏览器使用这些服务则体验极差,甚至无法使用;而如果要使用第三方软件则需要开启 Terminal,而且很多软件甚至无法支持控制手柄 —— 无论从哪方面讲这样的用户界面体验都糟糕透顶。
|
||||
|
||||
Valve 同时也几乎没有花任何力气去推广他们的新平台,而选择把一切都交由 OEM 厂商们去做。然而,几乎所有 OEM 合作商们要么是高端主机定制商,要么是电脑生产商,要么是廉价电脑公司(LCTT 译注:简而言之没有一家有大型宣传渠道)。在所有 OEM 中,只有戴尔是 PC 市场的大碗,也只有他们真正给 Steam Machines 做了广告宣传。
|
||||
|
||||
最终销量也不尽人意。截至 2016 年 6 月,7 个月间 Steam Controller 手柄的销量在包括捆绑销售的情况下仅销售 500,000 件。这让 Steam Machines 的零售情况差到只能被归类到十万俱乐部的最底部。对比已经存在的巨大 PC 和主机游戏平台,可以说销量极低。
|
||||
|
||||
### 事后诸葛亮
|
||||
|
||||
既然知道了 Steam Machines 的历史,我们又能否总结出失败的原因以及可能存在的翻身改进呢?
|
||||
|
||||
#### 视野与目标
|
||||
|
||||
Steam Machines 从来没搞清楚他们在市场里的定位究竟是什么,也从来没说清楚他们具体有何优势。从 PC 市场的角度来说,自己搭建台式机已经非常普及,并且往往可以让电脑可以匹配玩家自己的目标,同时升级性也非常好。从主机平台的角度来说,Steam Machines 又被主机本身的相对廉价所打败,虽然算上游戏可能稍微便宜一些,但主机上的用户体验也直白很多。
|
||||
|
||||
PC 用户会把多功能性看得很重,他们不仅能用电脑打游戏,也一样能办公和做各种各样的事情。即使 Steam Machines 也是跑着的 SteamOS 操作系统的自由的 Linux 电脑,但操作系统和市场宣传加深了 PC 玩家们对 Steam Machines 是不可定制的硬件、低价的、更接近主机的印象。即使这些 PC 用户能接受在客厅里购置一台 Steam Machines,他们也有 Steam Link 可以选择,而且很多更小型机比如 NUC 和 Mini-ITX 主板定制机可以让他们搭建更适合放在客厅里的电脑。SteamOS 软件也允许把这些硬件转变为 Steam Machines,但寻求灵活性和兼容性的用户通常都会使用一般 Linux 发行版或者 Windows。何况最近的 Windows 和 Linux 桌面环境都让维护一般用户的操作系统变得自动化和简单了。
|
||||
|
||||
电视主机用户们则把易用性放在第一。虽然近年来主机的功能也逐渐扩展,比如可以播放视频或者串流,但总体而言用户还是把即插即用即玩、不用担心兼容性和性能问题和低门槛放在第一。主机的使用寿命也往往较长,一般在 4-7 年左右,而统一固定的硬件也让游戏开发者们能针对其更好的优化和调试软件。现在刚刚兴起的中生代升级,例如天蝎和 PS 4 Pro 则可能会打破这样统一的游戏体验,但无论如何厂商还是会要求开发者们需要保证游戏在原机型上的体验。为了提高用户粘性,主机也会有自己的社交系统和独占游戏。而主机上的游戏也有实体版,以便将来重用或者二手转卖,这对零售商和用户都是好事儿。Steam Machines 则完全没有这方面的保证;即使长的像一台客厅主机,他们却有 PC 高昂的价格和复杂的硬件情况。
|
||||
|
||||
#### 妥协
|
||||
|
||||
综上所述,Steam Machines 可以说是“集大成者”,吸取了两边的缺点,又没有自己明确的定位。更糟糕的是 Steam Machines 还展现了 PC 和主机都没有的毛病,比如没有 AAA 大作,又没有 Netflix 这样的客户端。抛开这些不说,Valve 在提高他们产品这件事上几乎没有出力,甚至没有尝试着解决 PC 和主机两头定位矛盾这一点。
|
||||
|
||||
然而在有些事情上也许原本 PC 和主机就没法折中妥协。像图像设定和 Mod 等内容的加入会无法保证“傻瓜机”一般的可靠,而且系统下层的复杂性也会逐渐暴露。
|
||||
|
||||
而最复杂的是 Steam Machines 多变的硬件情况,这使得用户不仅要考虑价格还要考虑配置,还要考虑这个价格下和别的系统(PC 和主机)比起来划算与否。更关键的是,Valve 无论如何也应该做出某种自动硬件检测机制,这样玩家才能知道是否能玩某个游戏,而且这个测试既得简单明了,又要能支持 Steam 上几乎所有游戏。同时,Valve 还要操心未来游戏对配置需求的变化,比如2016 年的 "A" 等主机三年后该给什么评分呢?
|
||||
|
||||
#### Valve, 个人努力与公司结构
|
||||
|
||||
尽管 Valve 在 Steam 上创造了辉煌,但其公司的内部结构可能对于开发一个像 Steam Machines 一样的平台是有害的。他们几乎没有领导的自由办公结构,以及所有人都可以自由移动到想要工作的项目组里决定了他们具有极大的创新,研发,甚至开发能力。据说 Valve 只愿意招他们眼中的的 “顶尖人才”,通过极其严格的筛选标准,并通过让他们在自己认为“有意义”的项目里工作以保持热情。然而这种思路很可能是错误的;拉帮结派总是存在,而 G胖的话或许比公司手册上写的还管用,而又有人时不时会由于特殊原因被雇佣或解雇。
|
||||
|
||||
正因为如此,很多虽不闪闪发光甚至维护起来有些无聊但又需要大量时间的项目很容易枯萎。Valve 的客服已是被人诟病已久的毛病,玩家经常觉得被无视了,而 Valve 则经常不到万不得已、法律要求的情况下绝不行动:例如自动退款系统,就是在澳大利亚和欧盟法律的要求下才被加入的;更有目前还没结案的华盛顿州 CS:GO 物品在线赌博网站一案。
|
||||
|
||||
各种因素最后也反映在 Steam Machines 这一项目上。Valve 方面的跳票迫使一些合作方做出了尴尬的决定,比如戴尔提前一年发布了 Alienware Alpha 外观的 Steam Machine 就在一年后的正式发布时显得硬件状况落后了。跳票很可能也导致了游戏数量上的问题。开发者和硬件合作商的对跳票和最终毫无轰动的发布也不明朗。Valve 的 VR 平台干脆直接不支持 Linux,而直到最近,SteamVR 都风风火火迭代了好几次之后,SteamOS 和 Linux 依然不支持 VR。
|
||||
|
||||
#### “长线钓鱼”
|
||||
|
||||
尽管 Valve 方面对未来的规划毫无透露,有些人依然认为 Valve 在 Steam Machine 和 SteamOS 上是放长线钓大鱼。他们论点是 Steam 本身也是这样的项目 —— 一开始作为游戏补丁平台出现,到现在无敌的游戏零售和玩家社交网络。虽然 Valve 的独占游戏比如《半条命 2》和 《CS》 也帮助了 Steam 平台的传播。但现今我们完全无法看到 Valve 像当初对 Steam 那样上心 Steam Machines。同时现在 Steam Machines 也面临着 Steam 从没碰到过的激烈竞争。而这些竞争里自然也包含 Valve 自己的那些把 Windows 作为平台的 Steam 客户端。
|
||||
|
||||
#### 真正目的
|
||||
|
||||
鉴于投入在 Steam Machines 上的努力如此之少,有些人怀疑整个产品平台是不是仅仅作为某种博弈的筹码才被开发出来。原初 Steam Machines 就发家于担心微软和苹果通过自己的应用市场垄断游戏的反制手段当中,Valve 寄希望于 Steam Machines 可以在不备之时脱离那些操作系统的支持而运行,同时也是提醒开发者们,也许有一日整个 Steam 平台会独立出来。而当微软和苹果等方面的风口没有继续收紧的情况下,Valve 自然就放慢了开发进度。然而我不这样认为;Valve 其实已经花了不少精力与硬件商和游戏开发者们共同推行这件事,不可能仅仅是为了吓吓他人就终止项目。你可以把这件事想成,微软和 Valve 都在吓唬对方 —— 微软推出了突然收紧的 Windows 8 ,而 Valve 则展示了一下可以独立门户的能力。
|
||||
|
||||
但即使如此,谁能保证开发者不会愿意跟着微软的封闭环境跑了呢?万一微软最后能提供更好的待遇和用户群体呢?更何况,微软现在正大力推行 Xbox 和 Windows 的交叉和整合,甚至 Xbox 独占游戏也出现在 Windows 上,这一切都没有损害 Windows 原本的平台性定位 —— 谁还能说微软方面不是 Steam 的直接竞争对手呢?
|
||||
|
||||
还会有人说这一切一切都是为了推进 Linux 生态环境尽快接纳 PC 游戏,而 Steam Machines 只是想为此大力推一把。但如果是这样,那这个目的实在是性价比极低,因为本愿意支持 Linux 的自然会开发,而 Steam Machines 这一出甚至会让开发者对平台期待额落空从而伤害到他们。
|
||||
|
||||
### 大家眼中 Valve 曾经的机会
|
||||
|
||||
我认为 Steam Machines 的创意还是很有趣的,而也有一个与之匹配的市场,但就结果而言 Valve 投入的创意和努力还不够多,而定位模糊也伤害了这个产品。我认为 Steam Machines 的优势在于能砍掉 PC 游戏传统的复杂性,比如硬件问题、整机寿命和维护等;但又能拥有游戏便宜,可以打 Mod 等好处,而且也可以做各种定制化以满足用户需求。但他们必须要让产品的核心内容:价格、市场营销、机型产品线还有软件的质量有所保证才行。
|
||||
|
||||
我认为 Steam Machines 可以做出一点妥协,比如硬件升级性(尽管这一点还是有可能被保留下来的 —— 但也要极为小心整个过程对用户体验的影响)和产品选择性,来减少摩擦成本。PC 一直会是一个并列的选项。想给用户产品可选性带来的只有一个困境,成吨的质量低下的 Steam Machines 根本不能解决。Valve 得自己造一台旗舰机型来指明 Steam Machines 的方向。毫无疑问,Alienware 的产品是最接近理想目标的,但它说到底也不是 Valve 的官方之作。Valve 内部不乏优秀的工业设计人才,如果他们愿意投入足够多的重视,我认为结果也许会值得他们努力。而像戴尔和 HTC 这样的公司则可以用他们丰富的经验帮 Valve 制造成品。直接钦定 Steam Machines 的硬件周期,并且在期间只推出 1-2 台机型也有助于帮助解决问题,更不用说他们还可以依次和开发商们确立性能的基准线。我不知道 OEM 合作商们该怎么办;如果 Valve 专注于自己的几台设备里,OEM 们很可能会变得多余甚至拖平台后腿。
|
||||
|
||||
我觉得修复软件问题是最关键的。很多问题在严重拖着 Steam Machines 的后腿,比如缺少主机上遍地都是、又能轻易安装在 PC 上的的 Netflix 和 Twitch,那么即使做好了客厅体验问题依然是严重的败笔。即使 Valve 已经在逐步购买电影的版权以便在 Steam 上发售,我觉得用户还是会倾向于去使用已经在市场上建立口碑的一些流媒体服务。这些问题需要被严肃地对待,因为玩家日益倾向于把主机作为家庭影院系统的一部分。同时,修复 Steam 客户端和平台的问题也很重要,和更多第三方服务商合作增加内容应该会是个好主意。性能问题和 Linux 下的显卡问题也很严重,不过好在他们最近在慢慢进步。移植游戏也是个问题。类似 Feral Interactive 或者 Aspyr Media 这样的游戏移植商可以帮助扩展 Steam 的商店游戏数量,但联系开发者和出版社可能会有问题,而且这两家移植商经常在移植的容器上搞自己的花样。Valve 已经在帮助游戏工作室自己移植游戏了,比如 Rocket League,不过这种情况很少见,而且就算 Valve 去帮忙了,也是非常符合 Valve 拖拉的风格。而 AAA 大作这一块内容也绝不应该被忽略 —— 近来这方面的情况已经有极大好转了,虽然 Linux 平台的支持好了很多,但在玩家数量不够以及 Valve 为 Steam Machines 提供的开发帮助甚少的情况下,Bethesda 这样的开发商依然不愿意移植游戏;同时,也有像 Denuvo 一样缺乏数字版权管理的公司难以向 Steam Machines 移植游戏。
|
||||
|
||||
在我看来 Valve 需要在除了软件和硬件的地方也多花些功夫。如果他们只有一个机型的话,他们可以很方便的在硬件生产上贴点钱。这样 Steam Machines 的价格就能跻身主机的行列,而且还能比自己组装 PC 要便宜。针对正确的市场群体做营销也很关键,即便我们还不知道目标玩家应该是谁(我个人会对这样的 Steam Machines 感兴趣,而且我有一整堆已经在 Steam 上以相当便宜的价格买下的游戏)。最后,我觉得零售商们其实不会对 Valve 的计划很感冒,毕竟他们要靠卖和倒卖实体游戏赚钱。
|
||||
|
||||
就算 Valve 在产品和平台上采纳过这些改进,我也不知道怎样才能激活 Steam Machines 的全市场潜力。总的来说,Valve 不仅得学习自己的经验教训,还应该参考曾经有过类似尝试的厂商们,比如尝试依靠开放平台的 3DO 和 Pippin;又或者那些从台式机体验的竞争力退赛的那些公司,其实 Valve 如今的情况和他们也有几分相似。亦或者他们也可以观察一下任天堂 Switch —— 毕竟任天堂也在尝试跨界的创新。
|
||||
|
||||
_注解: 上述点子由 liamdawe 整理,所有的想法都由用户提交。_
|
||||
|
||||
本文是被一位访客提交的,我们欢迎大家前来[提交文章][1]。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.gamingonlinux.com/articles/user-editorial-steam-machines-steamos-after-a-year-in-the-wild.8474
|
||||
|
||||
作者:[calvin][a]
|
||||
译者:[Moelf](https://github.com/Moelf)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.gamingonlinux.com/profiles/5163
|
||||
[1]:https://www.gamingonlinux.com/submit-article/
|
||||
[2]:https://www.gamingonlinux.com/articles/steam-machines-steam-link-steam-controller-officially-released-steamos-sale.6201
|
||||
[3]:https://www.gamingonlinux.com/articles/valve-announces-steam-machines-you-can-win-one-too.2469
|
||||
@@ -0,0 +1,137 @@
|
||||
Android 在物联网方面能否像在移动终端一样成功?
|
||||
============================================================
|
||||
|
||||

|
||||
|
||||
*Android Things 让 IoT 如虎添翼*
|
||||
|
||||
### 我在 Android Things 上的最初 24 小时
|
||||
|
||||
正当我在开发一个基于 Android 的运行在树莓派 3 的物联网商业项目时,一些令人惊喜的事情发生了。谷歌发布了[Android Things][1] 的第一个预览版本,他们的 SDK 专门(目前)针对 3 个 SBC(单板计算机) —— 树莓派 3、英特尔 Edison 和恩智浦 Pico。说我一直在挣扎似乎有些轻描淡写 —— 树莓派上甚至没有一个成功的 Android 移植版本,我们在理想丰满,但是实践漏洞百出的内测版本上叫苦不迭。其中一个问题,同时也是不可原谅的问题是,它不支持触摸屏,甚至连 [Element14][2] 官方销售的也不支持。曾经我认为 Android 已经支持树莓派,更早时候 “[谷歌向 AOSP 项目发起提交][3]” 中提到过树莓派曾让所有人兴奋不已。所以当 2016 年 12 月 12 日谷歌发布 “Android Things” 及其 SDK 的时候,我马上闭门谢客,全身心地去研究了……
|
||||
|
||||
### 问题?
|
||||
|
||||
关于树莓派上的谷歌 Android 我遇到很多问题,我以前用 Android 做过许多开发,也做过一些树莓派项目,包括之前提到过的一个真正参与的。未来我会尝试解决这些问题,但是首先最重要的问题得到了解决 —— 有完整的 Android Studio 支持,树莓派成为你手里的另一个常规的 ADB 可寻址设备。好极了。Android Studio 强大而便利、十分易用的功能包括布局预览、调试系统、源码检查器、自动化测试等都可以真正的应用在 IoT 硬件上。这些好处怎么说都不过分。到目前为止,我在树莓派上的大部分工作都是通过 SSH 使用运行在树莓派上的编辑器(MC,如果你真的想知道)借助 Python 完成的。这是有效的,毫无疑问铁杆的 Pi/Python 粉丝或许会有更好的工作方式,而不是当前这种像极了 80 年代码农的软件开发模式。我的项目需要在控制树莓派的手机上编写 Android 软件,这真有点痛不欲生 —— 我使用 Android Studio 做“真正的” Android 开发,借助 SSH 做剩下的。但是有了“Android Things”之后,一切都结束了。
|
||||
|
||||
所有的示例代码都适用于这三种 SBC,树莓派只是其中之一。 `Build.DEVICE` 常量可以在运行时确定是哪一个,所以你会看到很多如下代码:
|
||||
|
||||
```
|
||||
public static String getGPIOForButton() {
|
||||
switch (Build.DEVICE) {
|
||||
case DEVICE_EDISON_ARDUINO:
|
||||
return "IO12";
|
||||
case DEVICE_EDISON:
|
||||
return "GP44";
|
||||
case DEVICE_RPI3:
|
||||
return "BCM21";
|
||||
case DEVICE_NXP:
|
||||
return "GPIO4_IO20";
|
||||
default:
|
||||
throw new IllegalStateException(“Unknown Build.DEVICE “ + Build.DEVICE);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
我对 GPIO 处理有浓厚的兴趣。 由于我只熟悉树莓派,我只能假定其它 SBC 工作方式相同,GPIO 只是一组引脚,可以定义为输入/输出,是连接物理外部世界的主要接口。 基于 Linux 的树莓派操作系统通过 Python 中的读取和写入方法提供了完整和便捷的支持,但对于 Android,您必须使用 NDK 编写 C++ 驱动程序,并通过 JNI 在 Java 中与这些驱动程序对接。 不是那么困难,但需要在你的构建链中维护额外的一些东西。 树莓派还为 I2C 指定了 2 个引脚:时钟和数据,因此需要额外的工作来处理它们。I2C 是真正酷的总线寻址系统,它通过串行化将许多独立的数据引脚转换成一个。 所以这里的优势是 —— Android Things 已经帮你完成了所有这一切。 你只需要 `read()` 和 `write() ` 你需要的任何 GPIO 引脚,I2C 同样容易:
|
||||
|
||||
```
|
||||
public class HomeActivity extends Activity {
|
||||
// I2C Device Name
|
||||
private static final String I2C_DEVICE_NAME = ...;
|
||||
// I2C Slave Address
|
||||
private static final int I2C_ADDRESS = ...;
|
||||
|
||||
private I2cDevice mDevice;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Attempt to access the I2C device
|
||||
try {
|
||||
PeripheralManagerService manager = new PeripheralManagerService();
|
||||
mDevice = manager.openI2cDevice(I2C_DEVICE_NAME, I2C_ADDRESS)
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Unable to access I2C device", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (mDevice != null) {
|
||||
try {
|
||||
mDevice.close();
|
||||
mDevice = null;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Unable to close I2C device", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Android Things 基于 Android 的哪个版本?
|
||||
|
||||
看起来是 Android 7.0,这样很好,因为我们可以继承 Android 所有以前版本的平板设计 UI、优化,安全加固等。它也带来了一个有趣的问题 —— 与应用程序必须单独管理不同,未来的平台应如何更新升级?请记住,这些设备可能无法连接到互联网。我们可能不便于连接蜂窝 / WiFi ,即便之前这些连接能用,但是有时不那么可靠。
|
||||
|
||||
另一个担心是,Android Things 仅仅是一个名字不同的 Android 分支版本,大部分都是一样的,和已经发布的 Arduino 一样,更像是为了市场营销而出现,而不是作为操作系统。不过可以放心,实际上通过[样例][4]可以看到,其中一些样例甚至使用了 SVG 图形作为资源,而不是传统的基于位图的图形(当然也能轻松处理) —— 这是一个非常新的 Android 创新。
|
||||
|
||||
不可避免地,与 Android Things 相比,普通的 Android 会有些不同。例如,权限问题。因为 Android Things 为固定硬件设计,在构建好之后,用户通常不会在这种设备上安装应用,所以在一定程序上减轻了这个问题,尽管当设备要求权限时是个问题 —— 因为它们没有 UI。解决方案是当应用在安装时给予所有需要的权限。 通常,这些设备只有一个应用,并且该应用从设备上电的那一刻就开始运行。
|
||||
|
||||

|
||||
|
||||
### Brillo 怎么了?
|
||||
|
||||
Brillo 是谷歌以前的 IoT 操作系统的代号,听起来很像 Android Things 的前身。 实际上现在你仍然能看到很多提及 Brillo 的地方,特别是在 GitHub Android Things 源码的文件夹名字中。 然而,它已经不复存在了。新王已经登基!
|
||||
|
||||
### UI 指南?
|
||||
|
||||
谷歌针对 Android 智能手机和平板电脑应用发布了大量指南,例如屏幕按钮间距等。 当然,你最好在可行的情况下遵循这些,但这已经不是本文应该考虑的范畴了。 缺省情况下什么也没有 —— 应用程序作者决定一切,这包括顶部状态栏,底部导航栏 —— 绝对是一切。 多年来谷歌一直在告诉 Android 应用程序的作者们绝不要在屏幕上放置返回按钮,因为平台将提供一个,因为 Android Things [可能甚至没有 UI!][5]。
|
||||
|
||||
### 智能手机上会有多少谷歌服务?
|
||||
|
||||
有一些,但不是所有。第一个预览版本没有蓝牙支持、没有 NFC,这两者都对物联网革命有重大贡献。 SBC 支持它们,所以我们应该不会等待太久。由于没有通知栏,因此不支持任何通知。没有地图。缺省没有软键盘,你必须自己安装一个键盘。由于没有 Play 商店,你只能艰难地通过 ADB 做这个和许多其他操作。
|
||||
|
||||
当为 Android Things 开发时,我试图为运行在手机上和树莓派上使用同一个 APK。这引发了一个错误,阻止它安装在除 Android Things 设备之外的任何设备:`com.google.android.things` 库不存在。 这有点用,因为只有 Android Things 设备需要这个,但它似乎是个限制,因为不仅智能手机或平板电脑上没有,连模拟器上也没有。似乎只能在物理 Android Things 设备上运行和测试您的 Android Things 应用程序……直到谷歌在 [G+ 谷歌的 IoT 开发人员社区][6]组中回答了我的问题,并提供了规避方案。但是,躲过初一,躲不过十五。
|
||||
|
||||
### 可以期待 Android Thing 生态演进到什么程度?
|
||||
|
||||
我期望看到移植更多传统的基于 Linux 服务器的应用程序,将 Android 限制在智能手机和平板电脑上没有意义。例如,Web 服务器突然变得非常有用。已经有一些了,但没有像重量级的 Apache 或 Nginx 的。物联网设备可以没有本地 UI,但通过浏览器管理它们当然是可行的,因此需要用这种方式呈现 Web 面板。类似的那些如雷贯耳的通讯应用程序 —— 它需要的仅是一个麦克风和扬声器,而且在理论上任何视频通话应用程序,如 Duo、Skype、FB 等都可行。这个演变能走多远目前只能猜测。会有 Play 商店吗?它们会展示广告吗?我们能够确保它们不会窥探我们,或被黑客控制它们么?从消费者的角度来看,物联网应该是具有触摸屏的网络连接设备,因为每个人都已经习惯于通过智能手机工作。
|
||||
|
||||
我还期望看到硬件的迅速发展 —— 特别是有更多的 SBC 拥有更低的成本。看看惊人的 5 美元树莓派 Zero,不幸的是,由于其有限的 CPU 和内存,几乎可以肯定不能运行 Android Things。多久之后像这样的设备才能运行 Android Things?这是很明显的,标杆已经设定,任何有追求的 SBC 制造商将瞄准 Android Things 的兼容性,规模经济也将波及到外围设备,如 23 美元的触摸屏。没人会购买不会播放 YouTube 的微波炉,你的洗碗机会在 eBay 上购买更多的清洁粉,因为它注意到你很少使用它……
|
||||
|
||||
然而,我不认为我们会过于冲昏头脑。了解一点 Android 架构有助于将其视为一个包罗万象的物联网操作系统。它仍然使用 Java,其垃圾回收机制导致的所有时序问题在过去几乎把它搞死。这仅仅是问题最少的部分。真正的实时操作系统依赖于可预测、准确和坚如磐石的时序,要么它就不能被用于“关键任务”。想想医疗应用、安全监视器,工业控制器等。使用 Android,如果宿主操作系统认为它需要,理论上可以在任何时候杀死您的活动/服务。这在手机上没那么糟糕 —— 用户可以重新启动应用程序,杀死其他应用程序,或重新启动手机。但心脏监视器就完全是另一码事。如果前台的活动/服务正在监视一个 GPIO 引脚,而这个信号没有被准确地处理,我们就完了。必须要做一些相当根本的改变让 Android 来支持这一点,到目前为止还没有迹象表明它已经在计划之中了。
|
||||
|
||||
### 这 24 小时
|
||||
|
||||
所以,回到我的项目。 我认为我会接管我已经完成和尽力能为的工作,等待不可避免的路障,并向 G+ 社区寻求帮助。 除了一些在非 Android Things 设备上如何运行程序的问题之外,没有其他问题。它运行得很好! 这个项目也使用了一些奇怪的东西,如自定义字体、高精定时器 —— 所有这些都在 Android Studio 中完美地展现。对我而言,可以打满分 —— 至少我能够开始做出实际原型,而不只是视频和截图。
|
||||
|
||||
### 蓝图
|
||||
|
||||
今天的物联网操作系统环境看起来非常零碎。 显然没有市场领导者,尽管炒作之声沸反连天,物联网仍然在草创阶段。 谷歌 Android 物联网能否像它在移动端那样取得成功?现在 Android 在移动方面的主导地位几近达到 90%。我相信如果真的如此,Android Things 的推出正是重要的一步。
|
||||
|
||||
记住所有的关于开放和封闭软件的战争,它们主要发生在从不授权的苹果和一直担心免费还不够充分的谷歌之间。那个老梗又来了,因为让苹果推出一个免费的物联网操作系统的构想就像让他们免费赠送下一代 iPhone 一样遥不可及。
|
||||
|
||||
物联网操作系统游戏是开放的,大家机遇共享,不过这个时候,封闭派甚至不会公布它们的开发工具箱……
|
||||
|
||||
前往 [Developer Preview][7]网站,立即获取 Android Things SDK 的副本。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://medium.com/@carl.whalley/will-android-do-for-iot-what-it-did-for-mobile-c9ac79d06c#.hxva5aqi2
|
||||
|
||||
作者:[Carl Whalley][a]
|
||||
译者:[firstadream](https://github.com/firstadream)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://medium.com/@carl.whalley
|
||||
[1]:https://developer.android.com/things/index.html
|
||||
[2]:https://www.element14.com/community/docs/DOC-78156/l/raspberry-pi-7-touchscreen-display
|
||||
[3]:http://www.androidpolice.com/2016/05/24/google-is-preparing-to-add-the-raspberry-pi-3-to-aosp-it-will-apparently-become-an-officially-supported-device/
|
||||
[4]:https://github.com/androidthings/sample-simpleui/blob/master/app/src/main/res/drawable/pinout_board_vert.xml
|
||||
[5]:https://developer.android.com/things/sdk/index.html
|
||||
[6]:https://plus.google.com/+CarlWhalley/posts/4tF76pWEs1D
|
||||
[7]:https://developer.android.com/things/preview/index.html
|
||||
@@ -0,0 +1,35 @@
|
||||
Ciao:云集成高级编排器
|
||||
============================================================
|
||||
|
||||
<ruby>云集成高级编排器<rt>Cloud Integrated Advanced Orchestrator</rt></ruby> (Ciao) 是一个新的负载调度程序,用来解决当前云操作系统项目的局限性。Ciao 提供了一个轻量级,完全基于 TLS 的最小配置。它是
|
||||
工作量无关的、易于更新、具有优化速度的调度程序,目前已针对 OpenStack 进行了优化。
|
||||
|
||||
其设计决策和创新方法在对安全性、可扩展性、可用性和可部署性的要求下进行:
|
||||
|
||||
- **可扩展性:** 初始设计目标是伸缩超过 5,000 个节点。因此,调度器架构用新的形式实现:
|
||||
- 在 ciao 中,决策制定是去中心化的。它基于拉取模型,允许计算节点从调度代理请求作业。调度程序总能知道启动器的容量,而不要求进行数据更新,并且将调度决策时间保持在最小。启动器异步向调度程序发送容量。
|
||||
- 持久化状态跟踪与调度程序决策制定相分离,它让调度程序保持轻量级。这种分离增加了可靠性、可扩展性和性能。结果是调度程序让出了权限并且这不是瓶颈。
|
||||
- **可用性:** 虚拟机、容器和裸机集成到一个调度器中。所有的负载都被视为平等公民。为了更易于使用,网络通过一个组件间最小化的异步协议进行简化,只需要最少的配置。Ciao 还包括一个新的、简单的 UI。所有的这些功能都集成到一起来简化安装、配置、维护和操作。
|
||||
- **轻松部署:** 升级应该是预期操作,而不是例外情况。这种新的去中心化状态的体系结构能够无缝升级。为了确保基础设施(例如 OpenStack)始终是最新的,它实现了持续集成/持续交付(CI/CD)模型。Ciao 的设计使得它可以立即杀死任何 Ciao 组件,更换它,并重新启动它,对可用性影响最小。
|
||||
- **安全性是必需的:** 与调度程序的连接总是加密的:默认情况下 SSL 是打开的,而不是关闭的。加密是从端到端:所有外部连接都需要 HTTPS,组件之间的内部通信是基于 TLS 的。网络支持的一体化保障了租户分离。
|
||||
|
||||
初步结果证明是显著的:在 65 秒内启动一万个 Docker 容器和五千个虚拟机。进一步优化还在进行。
|
||||
|
||||
- 文档:[https://clearlinux.org/documentation/ciao/ciao.html][3]
|
||||
- Github 链接: [https://github.com/01org/ciao(link is external)][1]
|
||||
- 邮件列表链接: [https://lists.clearlinux.org/mailman/listinfo/ciao-devel][2]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://clearlinux.org/ciao
|
||||
|
||||
作者:[ciao][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://clearlinux.org/ciao
|
||||
[1]:https://github.com/01org/ciao
|
||||
[2]:https://lists.clearlinux.org/mailman/listinfo/ciao-devel
|
||||
[3]:https://clearlinux.org/documentation/ciao/ciao.html
|
||||
@@ -0,0 +1,287 @@
|
||||
服务端 I/O 性能:Node、PHP、Java、Go 的对比
|
||||
============
|
||||
|
||||
了解应用程序的输入/输出(I/O)模型意味着理解应用程序处理其数据的载入差异,并揭示其在真实环境中表现。或许你的应用程序很小,在不承受很大的负载时,这并不是个严重的问题;但随着应用程序的流量负载增加,可能因为使用了低效的 I/O 模型导致承受不了而崩溃。
|
||||
|
||||
和大多数情况一样,处理这种问题的方法有多种方式,这不仅仅是一个择优的问题,而是对权衡的理解问题。 接下来我们来看看 I/O 到底是什么。
|
||||
|
||||

|
||||
|
||||
在本文中,我们将对 Node、Java、Go 和 PHP + Apache 进行对比,讨论不同语言如何构造其 I/O ,每个模型的优缺点,并总结一些基本的规律。如果你担心你的下一个 Web 应用程序的 I/O 性能,本文将给你最优的解答。
|
||||
|
||||
### I/O 基础知识: 快速复习
|
||||
|
||||
要了解 I/O 所涉及的因素,我们首先深入到操作系统层面复习这些概念。虽然看起来并不与这些概念直接打交道,但你会一直通过应用程序的运行时环境与它们间接接触。了解细节很重要。
|
||||
|
||||
#### 系统调用
|
||||
|
||||
首先是系统调用,其被描述如下:
|
||||
|
||||
* 程序(所谓“<ruby>用户端<rt>user land</rt></ruby>”)必须请求操作系统内核代表它执行 I/O 操作。
|
||||
* “<ruby>系统调用<rt>syscall</rt></ruby>”是你的程序要求内核执行某些操作的方法。这些实现的细节在操作系统之间有所不同,但基本概念是相同的。有一些具体的指令会将控制权从你的程序转移到内核(类似函数调用,但是使用专门用于处理这种情况的专用方式)。一般来说,系统调用会被阻塞,这意味着你的程序会等待内核返回(控制权到)你的代码。
|
||||
* 内核在所需的物理设备( 磁盘、网卡等 )上执行底层 I/O 操作,并回应系统调用。在实际情况中,内核可能需要做许多事情来满足你的要求,包括等待设备准备就绪、更新其内部状态等,但作为应用程序开发人员,你不需要关心这些。这是内核的工作。
|
||||
|
||||

|
||||
|
||||
#### 阻塞与非阻塞
|
||||
|
||||
上面我们提到过,系统调用是阻塞的,一般来说是这样的。然而,一些调用被归类为“非阻塞”,这意味着内核会接收你的请求,将其放在队列或缓冲区之类的地方,然后立即返回而不等待实际的 I/O 发生。所以它只是在很短的时间内“阻塞”,只需要排队你的请求即可。
|
||||
|
||||
举一些 Linux 系统调用的例子可能有助于理解:
|
||||
|
||||
- `read()` 是一个阻塞调用 - 你传递一个句柄,指出哪个文件和缓冲区在哪里传送它所读取的数据,当数据就绪时,该调用返回。这种方式的优点是简单友好。
|
||||
- 分别调用 `epoll_create()`、`epoll_ctl()` 和 `epoll_wait()` ,你可以创建一组句柄来侦听、添加/删除该组中的处理程序、然后阻塞直到有任何事件发生。这允许你通过单个线程有效地控制大量的 I/O 操作,但是现在谈这个还太早。如果你需要这个功能当然好,但须知道它使用起来是比较复杂的。
|
||||
|
||||
了解这里的时间差异的数量级是很重要的。假设 CPU 内核运行在 3GHz,在没有进行 CPU 优化的情况下,那么它每秒执行 30 亿次<ruby>周期<rt>cycle</rt></ruby>(即每纳秒 3 个周期)。非阻塞系统调用可能需要几十个周期来完成,或者说 “相对少的纳秒” 时间完成。而一个被跨网络接收信息所阻塞的系统调用可能需要更长的时间 - 例如 200 毫秒(1/5 秒)。这就是说,如果非阻塞调用需要 20 纳秒,阻塞调用需要 2 亿纳秒。你的进程因阻塞调用而等待了 1000 万倍的时长!
|
||||
|
||||

|
||||
|
||||
内核既提供了阻塞 I/O (“从网络连接读取并给出数据”),也提供了非阻塞 I/O (“告知我何时这些网络连接具有新数据”)的方法。使用的是哪种机制对调用进程的阻塞时长有截然不同的影响。
|
||||
|
||||
#### 调度
|
||||
|
||||
关键的第三件事是当你有很多线程或进程开始阻塞时会发生什么。
|
||||
|
||||
根据我们的理解,线程和进程之间没有很大的区别。在现实生活中,最显著的性能相关的差异在于,由于线程共享相同的内存,而进程每个都有自己的内存空间,使得单独的进程往往占用更多的内存。但是当我们谈论<ruby>调度<rt>Scheduling</rt></ruby>时,它真正归结为一类事情(线程和进程类同),每个都需要在可用的 CPU 内核上获得一段执行时间。如果你有 300 个线程运行在 8 个内核上,则必须将时间分成几份,以便每个线程和进程都能分享它,每个运行一段时间,然后交给下一个。这是通过 “<ruby>上下文切换<rt>context switch</rt></ruby>” 完成的,可以使 CPU 从运行到一个线程/进程到切换下一个。
|
||||
|
||||
这些上下文切换也有相关的成本 - 它们需要一些时间。在某些快速的情况下,它可能小于 100 纳秒,但根据实际情况、处理器速度/体系结构、CPU 缓存等,偶见花费 1000 纳秒或更长时间。
|
||||
|
||||
而线程(或进程)越多,上下文切换就越多。当我们涉及数以千计的线程时,每个线程花费数百纳秒,就会变得很慢。
|
||||
|
||||
然而,非阻塞调用实质上是告诉内核“仅在这些连接之一有新的数据或事件时再叫我”。这些非阻塞调用旨在有效地处理大量 I/O 负载并减少上下文交换。
|
||||
|
||||
这些你明白了么?现在来到了真正有趣的部分:我们来看看一些流行的语言对那些工具的使用,并得出关于易用性和性能之间权衡的结论,以及一些其他有趣小东西。
|
||||
|
||||
声明,本文中显示的示例是零碎的(片面的,只能体现相关的信息); 数据库访问、外部缓存系统( memcache 等等)以及任何需要 I/O 的东西都将执行某种类型的 I/O 调用,其实质与上面所示的简单示例效果相同。此外,对于将 I/O 描述为“阻塞”( PHP、Java )的情况,HTTP 请求和响应读取和写入本身就是阻塞调用:系统中隐藏着更多 I/O 及其伴生的性能问题需要考虑。
|
||||
|
||||
为一个项目选择编程语言要考虑很多因素。甚至当你只考虑效率时,也有很多因素。但是,如果你担心你的程序将主要受到 I/O 的限制,如果 I/O 性能影响到项目的成败,那么这些是你需要了解的。
|
||||
|
||||
### “保持简单”方法:PHP
|
||||
|
||||
早在 90 年代,很多人都穿着 [Converse][1] 鞋,用 Perl 写着 CGI 脚本。然后 PHP 来了,就像一些人喜欢咒骂的一样,它使得动态网页更容易。
|
||||
|
||||
PHP 使用的模型相当简单。虽有一些出入,但你的 PHP 服务器基本上是这样:
|
||||
|
||||
HTTP 请求来自用户的浏览器,并访问你的 Apache Web 服务器。Apache 为每个请求创建一个单独的进程,有一些优化方式可以重新使用它们,以最大限度地减少创建次数( 相对而言,创建进程较慢 )。Apache 调用 PHP 并告诉它运行磁盘上合适的 `.php` 文件。PHP 代码执行并阻塞 I/O 调用。你在 PHP 中调用 `file_get_contents()` ,其底层会调用 `read()` 系统调用并等待结果。
|
||||
|
||||
当然,实际的代码是直接嵌入到你的页面,并且该操作被阻塞:
|
||||
|
||||
```
|
||||
<?php
|
||||
|
||||
// blocking file I/O
|
||||
$file_data = file_get_contents(‘/path/to/file.dat’);
|
||||
|
||||
// blocking network I/O
|
||||
$curl = curl_init('http://example.com/example-microservice');
|
||||
$result = curl_exec($curl);
|
||||
|
||||
// some more blocking network I/O
|
||||
$result = $db->query('SELECT id, data FROM examples ORDER BY id DESC limit 100');
|
||||
|
||||
?>
|
||||
|
||||
```
|
||||
|
||||
关于如何与系统集成,就像这样:
|
||||
|
||||

|
||||
|
||||
很简单:每个请求一个进程。 I/O 调用就阻塞。优点是简单可工作,缺点是,同时与 20,000 个客户端连接,你的服务器将会崩溃。这种方法不能很好地扩展,因为内核提供的用于处理大容量 I/O (epoll 等) 的工具没有被使用。 雪上加霜的是,为每个请求运行一个单独的进程往往会使用大量的系统资源,特别是内存,这通常是你在这样的场景中遇到的第一个问题。
|
||||
|
||||
_注意:Ruby 使用的方法与 PHP 非常相似,在大致的方面上,它们可以被认为是相同的。_
|
||||
|
||||
### 多线程方法: Java
|
||||
|
||||
就在你购买你的第一个域名,在某个句子后很酷地随机说出 “dot com” 的那个时候,Java 来了。而 Java 具有内置于该语言中的多线程功能,它非常棒(特别是在创建时)。
|
||||
|
||||
大多数 Java Web 服务器通过为每个请求启动一个新的执行线程,然后在该线程中最终调用你(作为应用程序开发人员)编写的函数。
|
||||
|
||||
在 Java Servlet 中执行 I/O 往往看起来像:
|
||||
|
||||
```
|
||||
public void doGet(HttpServletRequest request,
|
||||
HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
|
||||
// blocking file I/O
|
||||
InputStream fileIs = new FileInputStream("/path/to/file");
|
||||
|
||||
// blocking network I/O
|
||||
URLConnection urlConnection = (new URL("http://example.com/example-microservice")).openConnection();
|
||||
InputStream netIs = urlConnection.getInputStream();
|
||||
|
||||
// some more blocking network I/O
|
||||
out.println("...");
|
||||
}
|
||||
```
|
||||
|
||||
由于我们上面的 `doGet` 方法对应于一个请求,并且在其自己的线程中运行,而不是每个请求一个单独的进程,申请自己的内存。这样有一些好处,比如在线程之间共享状态、缓存数据等,因为它们可以访问彼此的内存,但是它与调度的交互影响与之前的 PHP 的例子几乎相同。每个请求获得一个新线程,该线程内的各种 I/O 操作阻塞在线程内,直到请求被完全处理为止。线程被池化以最小化创建和销毁它们的成本,但是数千个连接仍然意味着数千个线程,这对调度程序是不利的。
|
||||
|
||||
重要的里程碑出现在 Java 1.4 版本(以及 1.7 的重要升级)中,它获得了执行非阻塞 I/O 调用的能力。大多数应用程序、web 应用和其它用途不会使用它,但至少它是可用的。一些 Java Web 服务器尝试以各种方式利用这一点;然而,绝大多数部署的 Java 应用程序仍然如上所述工作。
|
||||
|
||||

|
||||
|
||||
肯定有一些很好的开箱即用的 I/O 功能,Java 让我们更接近,但它仍然没有真正解决当你有一个大量的 I/O 绑定的应用程序被数千个阻塞线程所压垮的问题。
|
||||
|
||||
### 无阻塞 I/O 作为一等公民: Node
|
||||
|
||||
当更好的 I/O 模式来到 Node.js,阻塞才真正被解决。任何一个曾听过 Node 简单介绍的人都被告知这是“非阻塞”,可以有效地处理 I/O。这在一般意义上是正确的。但在细节中则不尽然,而且当在进行性能工程时,这种巫术遇到了问题。
|
||||
|
||||
Node 实现的范例基本上不是说 “在这里写代码来处理请求”,而是说 “在这里写代码来**开始**处理请求”。每次你需要做一些涉及到 I/O 的操作,你会创建一个请求并给出一个回调函数,Node 将在完成之后调用该函数。
|
||||
|
||||
在请求中执行 I/O 操作的典型 Node 代码如下所示:
|
||||
|
||||
```
|
||||
http.createServer(function(request, response) {
|
||||
fs.readFile('/path/to/file', 'utf8', function(err, data) {
|
||||
response.end(data);
|
||||
});
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
你可以看到,这里有两个回调函数。当请求开始时,第一个被调用,当文件数据可用时,第二个被调用。
|
||||
|
||||
这样做的基本原理是让 Node 有机会有效地处理这些回调之间的 I/O 。一个更加密切相关的场景是在 Node 中进行数据库调用,但是我不会在这个例子中啰嗦,因为它遵循完全相同的原则:启动数据库调用,并给 Node 一个回调函数,它使用非阻塞调用单独执行 I/O 操作,然后在你要求的数据可用时调用回调函数。排队 I/O 调用和让 Node 处理它然后获取回调的机制称为“事件循环”。它工作的很好。
|
||||
|
||||

|
||||
|
||||
然而,这个模型有一个陷阱,究其原因,很多是与 V8 JavaScript 引擎(Node 用的是 Chrome 浏览器的 JS 引擎)如何实现的有关^注1 。你编写的所有 JS 代码都运行在单个线程中。你可以想想,这意味着当使用高效的非阻塞技术执行 I/O 时,你的 JS 可以在单个线程中运行计算密集型的操作,每个代码块都会阻塞下一个。可能出现这种情况的一个常见例子是以某种方式遍历数据库记录,然后再将其输出到客户端。这是一个示例,展示了其是如何工作:
|
||||
|
||||
```
|
||||
var handler = function(request, response) {
|
||||
|
||||
connection.query('SELECT ...', function (err, rows) {
|
||||
|
||||
if (err) { throw err };
|
||||
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
// do processing on each row
|
||||
}
|
||||
|
||||
response.end(...); // write out the results
|
||||
|
||||
})
|
||||
|
||||
};
|
||||
```
|
||||
|
||||
虽然 Node 确实有效地处理了 I/O ,但是上面的例子中 `for` 循环是在你的唯一的一个主线程中占用 CPU 周期。这意味着如果你有 10,000 个连接,则该循环可能会使你的整个应用程序像爬行般缓慢,具体取决于其会持续多久。每个请求必须在主线程中分享一段时间,一次一段。
|
||||
|
||||
这整个概念的前提是 I/O 操作是最慢的部分,因此最重要的是要有效地处理这些操作,即使这意味着要连续进行其他处理。这在某些情况下是正确的,但不是全部。
|
||||
|
||||
另一点是,虽然这只是一个观点,但是写一堆嵌套的回调可能是相当令人讨厌的,有些则认为它使代码更难以追踪。在 Node 代码中看到回调嵌套 4 层、5 层甚至更多层并不罕见。
|
||||
|
||||
我们再次来权衡一下。如果你的主要性能问题是 I/O,则 Node 模型工作正常。然而,它的关键是,你可以在一个处理 HTTP 请求的函数里面放置 CPU 密集型的代码,而且不小心的话会导致每个连接都很慢。
|
||||
|
||||
### 最自然的非阻塞:Go
|
||||
|
||||
在我进入 Go 部分之前,我应该披露我是一个 Go 的粉丝。我已经在许多项目中使用过它,我是一个其生产力优势的公开支持者,我在我的工作中使用它。
|
||||
|
||||
那么,让我们来看看它是如何处理 I/O 的。Go 语言的一个关键特征是它包含自己的调度程序。在 Go 中,不是每个执行线程对应于一个单一的 OS 线程,其通过一种叫做 “<ruby>协程<rt>goroutine</rt></ruby>” 的概念来工作。而 Go 的运行时可以将一个协程分配给一个 OS 线程,使其执行或暂停它,并且它不与一个 OS 线程相关联——这要基于那个协程正在做什么。来自 Go 的 HTTP 服务器的每个请求都在单独的协程中处理。
|
||||
|
||||
调度程序的工作原理如图所示:
|
||||
|
||||

|
||||
|
||||
在底层,这是通过 Go 运行时中的各个部分实现的,它通过对请求的写入/读取/连接等操作来实现 I/O 调用,将当前协程休眠,并当采取进一步动作时唤醒该协程。
|
||||
|
||||
从效果上看,Go 运行时做的一些事情与 Node 做的没有太大不同,除了回调机制是内置到 I/O 调用的实现中,并自动与调度程序交互。它也不会受到必须让所有处理程序代码在同一个线程中运行的限制,Go 将根据其调度程序中的逻辑自动将协程映射到其认为适当的 OS 线程。结果是这样的代码:
|
||||
|
||||
```
|
||||
func ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// the underlying network call here is non-blocking
|
||||
rows, err := db.Query("SELECT ...")
|
||||
|
||||
for _, row := range rows {
|
||||
// do something with the rows,
|
||||
// each request in its own goroutine
|
||||
}
|
||||
|
||||
w.Write(...) // write the response, also non-blocking
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
如上所述,我们重构基本的代码结构为更简化的方式,并在底层仍然实现了非阻塞 I/O。
|
||||
|
||||
在大多数情况下,最终是“两全其美”的。非阻塞 I/O 用于所有重要的事情,但是你的代码看起来像是阻塞,因此更容易理解和维护。Go 调度程序和 OS 调度程序之间的交互处理其余部分。这不是完整的魔法,如果你建立一个大型系统,那么值得我们来看看有关它的工作原理的更多细节;但与此同时,你获得的“开箱即用”的环境可以很好地工作和扩展。
|
||||
|
||||
Go 可能有其缺点,但一般来说,它处理 I/O 的方式不在其中。
|
||||
|
||||
### 谎言,可恶的谎言和基准
|
||||
|
||||
对这些各种模式的上下文切换进行准确的定时是很困难的。我也可以认为这对你来说不太有用。相反,我会给出一些比较这些服务器环境的整个 HTTP 服务器性能的基本基准。请记住,影响整个端到端 HTTP 请求/响应路径的性能有很多因素,这里提供的数字只是我将一些样本放在一起进行基本比较的结果。
|
||||
|
||||
对于这些环境中的每一个,我写了适当的代码在一个 64k 文件中读取随机字节,在其上运行了一个 SHA-256 哈希 N 次( N 在 URL 的查询字符串中指定,例如 .../test.php?n=100),并打印出结果十六进制散列。我选择这样做,是因为使用一些一致的 I/O 和受控的方式来运行相同的基准测试是一个增加 CPU 使用率的非常简单的方法。
|
||||
|
||||
有关使用的环境的更多细节,请参阅 [基准说明][3] 。
|
||||
|
||||
首先,我们来看一些低并发的例子。运行 2000 次迭代,300 个并发请求,每个请求只有一个散列(N = 1),结果如下:
|
||||
|
||||

|
||||
|
||||
*时间是在所有并发请求中完成请求的平均毫秒数。越低越好。*
|
||||
|
||||
仅从一张图很难得出结论,但是对我来说,似乎在大量的连接和计算量上,我们看到时间更多地与语言本身的一般执行有关,对于 I/O 更是如此。请注意,那些被视为“脚本语言”的语言(松散类型,动态解释)执行速度最慢。
|
||||
|
||||
但是,如果我们将 N 增加到 1000,仍然有 300 个并发请求,相同的任务,但是哈希迭代是 1000 倍(显着增加了 CPU 负载):
|
||||
|
||||

|
||||
|
||||
*时间是在所有并发请求中完成请求的平均毫秒数。越低越好。*
|
||||
|
||||
突然间, Node 性能显著下降,因为每个请求中的 CPU 密集型操作都相互阻塞。有趣的是,在这个测试中,PHP 的性能要好得多(相对于其他的),并且打败了 Java。(值得注意的是,在 PHP 中,SHA-256 实现是用 C 编写的,在那个循环中执行路径花费了更多的时间,因为现在我们正在进行 1000 个哈希迭代)。
|
||||
|
||||
现在让我们尝试 5000 个并发连接(N = 1) - 或者是我可以发起的最大连接。不幸的是,对于大多数这些环境,故障率并不显着。对于这个图表,我们来看每秒的请求总数。 _越高越好_ :
|
||||
|
||||

|
||||
|
||||
*每秒请求数。越高越好。*
|
||||
|
||||
这个图看起来有很大的不同。我猜测,但是看起来像在高连接量时,产生新进程所涉及的每连接开销以及与 PHP + Apache 相关联的附加内存似乎成为主要因素,并阻止了 PHP 的性能。显然,Go 是这里的赢家,其次是 Java,Node,最后是 PHP。
|
||||
|
||||
虽然与你的整体吞吐量相关的因素很多,并且在应用程序之间也有很大的差异,但是你对底层发生什么的事情以及所涉及的权衡了解更多,你将会得到更好的结果。
|
||||
|
||||
### 总结
|
||||
|
||||
以上所有这一切,很显然,随着语言的发展,处理大量 I/O 的大型应用程序的解决方案也随之发展。
|
||||
|
||||
为了公平起见,PHP 和 Java,尽管这篇文章中的描述,确实 [实现了][4] 在 [ web 应用程序][7] 中 [可使用的][6] [ 非阻塞 I/O][5] 。但是这些方法并不像上述方法那么常见,并且需要考虑使用这种方法来维护服务器的随之而来的操作开销。更不用说你的代码必须以与这些环境相适应的方式进行结构化;你的 “正常” PHP 或 Java Web 应用程序通常不会在这样的环境中进行重大修改。
|
||||
|
||||
作为比较,如果我们考虑影响性能和易用性的几个重要因素,我们得出以下结论:
|
||||
|
||||
| 语言 | 线程与进程 | 非阻塞 I/O | 使用便捷性 |
|
||||
| --- | --- | --- | --- |
|
||||
| PHP | 进程 | 否 | |
|
||||
| Java | 线程 | 可用 | 需要回调 |
|
||||
| Node.js | 线程 | 是 | 需要回调 |
|
||||
| Go | 线程 (协程) | 是 | 不需要回调 |
|
||||
|
||||
线程通常要比进程有更高的内存效率,因为它们共享相同的内存空间,而进程则没有。结合与非阻塞 I/O 相关的因素,我们可以看到,至少考虑到上述因素,当我们从列表往下看时,与 I/O 相关的一般设置得到改善。所以如果我不得不在上面的比赛中选择一个赢家,那肯定会是 Go。
|
||||
|
||||
即使如此,在实践中,选择构建应用程序的环境与你的团队对所述环境的熟悉程度以及你可以实现的总体生产力密切相关。因此,每个团队都深入并开始在 Node 或 Go 中开发 Web 应用程序和服务可能就没有意义。事实上,寻找开发人员或你内部团队的熟悉度通常被认为是不使用不同语言和/或环境的主要原因。也就是说,过去十五年来,时代已经发生了变化。
|
||||
|
||||
希望以上内容可以帮助你更清楚地了解底层发生的情况,并为你提供如何处理应用程序的现实可扩展性的一些想法。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.toptal.com/back-end/server-side-io-performance-node-php-java-go
|
||||
|
||||
作者:[BRAD PEABODY][a]
|
||||
译者:[MonkeyDEcho](https://github.com/MonkeyDEcho)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.toptal.com/resume/brad-peabody
|
||||
[1]:https://www.pinterest.com/pin/414401603185852181/
|
||||
[2]:http://www.journaldev.com/7462/node-js-architecture-single-threaded-event-loop
|
||||
[3]:https://peabody.io/post/server-env-benchmarks/
|
||||
[4]:http://reactphp.org/
|
||||
[5]:http://amphp.org/
|
||||
[6]:http://undertow.io/
|
||||
[7]:https://netty.io/
|
||||
@@ -0,0 +1,86 @@
|
||||
促使项目团队作出改变的五步计划
|
||||
============================================================
|
||||
|
||||

|
||||
|
||||
目的是任何团队组建的首要之事。如果一个人足以实现那个目的,那么就没有必要组成团队。而且如果没有重要目标,你根本不需要一个团队。但只要任务需要的专业知识比一个人所拥有的更多,我们就会遇到集体参与的问题——如果处理不当,会使你脱离正轨。
|
||||
|
||||
想象一群人困在洞穴中。没有一个人具备如何出去的全部知识,所以每个人要协作,心路常开,在想要做的事情上尽力配合。当(且仅当)组建了适当的工作团队之后,才能为实现团队的共同目标创造出合适的环境。
|
||||
|
||||
但确实有人觉得待在洞穴中很舒适而且只想待在那里。在组织里,领导者们如何掌控那些实际上抵触改善、待在洞穴中觉得舒适的人?同时该如何找到拥有共同目标但是不在自己组织的人?
|
||||
|
||||
我从事指导国际销售培训,刚开始甚至很少有人认为我的工作有价值。所以,我想出一套使他们信服的战术。那个战术非常成功以至于我决定深入研究它并与各位[分享][2]。
|
||||
|
||||
### 获得支持
|
||||
|
||||
为了建立公司强大的企业文化,有人会反对改变,并且从幕后打压任何改变的提议。他们希望每个人都待在那个舒适的洞穴里。例如,当我第一次接触到海外销售培训,我受到了一些关键人物的严厉阻挠。他们迫使其他人相信某个东京人做不了销售培训——只要基本的产品培训就行了。
|
||||
|
||||
尽管我最终解决了这个问题,但我那时候真的不知道该怎么办。所以,我开始研究顾问们在改变公司里抗拒改变的人的想法这个问题上该如何给出建议。从学者 [Laurence Haughton][3] 的研究中,我发现一般对于改变的提议,组织中 83% 的人最开始不会支持你。大约 17% _会_从一开始就支持你,但是只要看到一个实验案例成功之后,他们觉得这个主意安全可行了,60% 的人会支持你。最后,有部分人会反对任何改变,无论它有多棒。
|
||||
|
||||
我研究的步骤:
|
||||
|
||||
* 从试验项目开始
|
||||
* 开导洞穴人
|
||||
* 快速跟进
|
||||
* 开导洞穴首领
|
||||
* 全局展开
|
||||
|
||||
### 1、 从试验项目开始
|
||||
|
||||
找到高价值且成功率较高的项目——而不是大的、成本高的、周期长的、全局的行动。然后,找到能看到项目价值、理解它的价值并能为之奋斗的关键人物。这些人不应该只是“老好人”或者“朋友”;他们必须相信项目的目标而且拥有推进项目的能力或经验。不要急于求成。只要足够支持你研究并保持进度即可。
|
||||
|
||||
个人而言,我在新加坡的一个小型车辆代理商那里举办了自己的第一场销售研讨会。虽然并不是特别成功,但足以让人们开始讨论销售训练会达到怎样的效果。那时候的我困在洞穴里(那是一份我不想做的工作)。这个试验销售训练是我走出困境的蓝图。
|
||||
|
||||
### 2、 开导洞穴人
|
||||
|
||||
洞穴(CAVE)实际上是我从 Laurence Haughton 那里听来的缩略词。它代表着 Citizens Against Virtually Everything。(LCTT 译注,此处一语双关前文提及的洞穴。)
|
||||
|
||||
你得辨别这些人,因为他们会暗地里阻挠项目的进展,特别是早期脆弱的时候。他们容易黑化:总是消极。他们频繁使用“但是”、“如果”和“为什么”,只是想推脱你。他们询问轻易不可得的细节信息。他们花费过多的时间在问题上,而不是寻找解决方案。他们认为每个失败都是一个趋势。他们总是对人而不是对事。他们作出反对建议的陈述却又不能简单确认。
|
||||
|
||||
避开洞穴人;不要让他们太早加入项目的讨论。他们固守成见,因为他们看不到改变所具有的价值。他们安居于洞穴,所以试着让他们去做些其他事。你应该找出我上面提到那 17% 的人群中的关键人物,那些想要改变的人,并且跟他们开一个非常隐秘的准备会。
|
||||
|
||||
我在五十铃汽车(股东之一是通用汽车公司)的时候,销售训练项目开始于一个销往世界上其他小国家的合资分销商,主要是非洲、南亚、拉丁美洲和中东。我的个人团队由通用汽车公司雪佛兰的人、五十铃产品经理和分公司的销售计划员工组成。隔绝其他任何人于这个圈子之外。
|
||||
|
||||
### 3、 快速跟进
|
||||
|
||||
洞穴人总是慢吞吞的,那么你就迅速行动起来。如果你在他们参与之前就有了小成就的经历,他们对你团队产生消极影响的能力将大大减弱——你要在他们提出之前就解决他们必然反对的问题。再一次,选择一个成功率高的试验项目,很快能出结果的。然后宣传成功,就像广告上的加粗标题。
|
||||
|
||||
当我在新加坡研讨会上所言开始流传时,其他地区开始意识到销售训练的好处。仅在新加坡研讨会之后,我就被派到马来西亚开展了四次以上。
|
||||
|
||||
### 4、 开导洞穴首领
|
||||
|
||||
只要你取得了第一个小项目的成功,就针对能影响洞穴首领的关键人物推荐项目。让团队继续该项目以告诉关键人物成功的经历。一线人员甚至顾客也能提供有力的证明。 洞穴管理者往往只着眼于销量和收益,那么就宣扬项目在降低开支、减少浪费和增加销量方面的价值。
|
||||
|
||||
自新加坡的第一次研讨会及之后,我向直接掌握了五十铃销售渠道的前线销售部门员工和通用汽车真正想看到进展的人极力宣传他们的成功。当他们接受了之后,他们会向上级提出培训请求并让其看到分公司销量的提升。
|
||||
|
||||
### 5、 全局展开
|
||||
|
||||
一旦一把手站在了自己这边,立马向整个组织宣告成功的试验项目。讨论项目的扩展。
|
||||
|
||||
用上面的方法,在 21 年的职业生涯中,我在世界各地超过 60 个国家举办了研讨会。我确实走出了洞穴——并且真的看到了广阔的世界。
|
||||
|
||||
(题图:opensource.com)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
Ron McFarland - Ron McFarland 已在日本工作 40 年,从事国际销售、销售管理和在世界范围内扩展销售业务 30 载有余。他曾去过或就职于 80 多个国家。在过去的 14 年里, Ron 为总部位于东京的日本硬件切割厂在美国和欧洲各地建立分销商。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/open-organization/17/1/escape-the-cave
|
||||
|
||||
作者:[Ron McFarland][a]
|
||||
译者:[XYenChi](https://github.com/XYenChi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/ron-mcfarland
|
||||
[1]:https://opensource.com/open-organization/17/1/escape-the-cave?rate=dBJIKVJy720uFj0PCfa1JXDZKkMwozxV8TB2qJnoghM
|
||||
[2]:http://www.slideshare.net/RonMcFarland1/creating-change-58994683
|
||||
[3]:http://www.laurencehaughton.com/
|
||||
[4]:https://opensource.com/user/68021/feed
|
||||
[5]:https://opensource.com/open-organization/17/1/escape-the-cave#comments
|
||||
[6]:https://opensource.com/users/ron-mcfarland
|
||||
@@ -0,0 +1,777 @@
|
||||
详解 Ubuntu snap 包的制作过程
|
||||
============================================================
|
||||
|
||||
> 如果你看过译者以前翻译的 snappy 文章,不知有没有感觉相关主题都是浅尝辄止,讲得不够透彻,看得也不太过瘾?如果有的话,相信这篇详细讲解如何从零开始制作一个 snap 包的文章应该不会让你失望。
|
||||
|
||||
在这篇文章中,我们将看到如何为名为 [timg][1] 的实用程序制作对应的 snap 包。如果这是你第一次听说 snap 安装包,你可以先看看 [如何创建你的第一个 snap 包][2]。
|
||||
|
||||
今天我们将学习以下有关使用 snapcraft 制作 snap 包的内容:
|
||||
|
||||
* [timg][3] 源码中的 Makefile 文件是手工编写,我们需要修改一些 [make 插件参数][4]。
|
||||
* 这个程序是用 C++ 语言写的,依赖几个额外的库文件。我们需要把相关的代码添加到 snap 包中。
|
||||
* [严格限制还是传统限制][38]?我们将会讨论如何在它们之间进行选择。
|
||||
|
||||
首先,我们了解下 [timg][5] 有什么用?
|
||||
|
||||
### 背景
|
||||
|
||||
Linux 终端模拟器已经变得非常炫酷,并且还能显示颜色!
|
||||
|
||||
![1.png-19.9kB][6]
|
||||
|
||||
除了标准的颜色,大多数终端模拟器(如上图显示的 GNOME 终端)都支持真彩色(1600 万种颜色)。
|
||||
|
||||
![图片.png-61.9kB][7]
|
||||
|
||||
是的!终端模拟器已经支持真彩色了!从这个页面“ [多个终端和终端应用程序已经支持真彩色(1600 万种颜色)][8]” 可以获取 AWK 代码自己进行测试。你可以看到在代码中使用了一些 [转义序列][9] 来指定 RGB 的值(256 * 256 * 256 ~= 1600 万种颜色)。
|
||||
|
||||
### timg 是什么?
|
||||
|
||||
好了,言归正传,[timg][10] 有什么用?它能将输入的图片重新调整为终端窗口字符所能显示范围的大小(比如:80 x 25),然后在任何分辨率的终端窗口用彩色字符显示图像。
|
||||
|
||||
![图片.png-37.3kB][11]
|
||||
|
||||
这幅图用彩色块字符显示了 [Ubuntu 的 logo][12],原图是一个 PNG 格式的文件。
|
||||
|
||||
![图片.png-165kB][13]
|
||||
|
||||
这是 [@Doug8888 拍摄的花][14]。
|
||||
|
||||
如果你通过远程连接服务器来管理自己的业务,并想要查看图像文件,那么 [timg][15] 将会特别有用。
|
||||
|
||||
除了静态图片,[timg][16] 同样也可以显示 gif 动图。
|
||||
|
||||
那么让我们开始 snap 之旅吧!
|
||||
|
||||
### 熟悉 timg 的源码
|
||||
|
||||
[timg][17] 的源码可以在 [https://github.com/hzeller/timg][18] 找到。让我们试着手动编译它,以了解它有什么需求。
|
||||
|
||||
![图片.png-128.4kB][19]
|
||||
|
||||
`Makefile` 在 `src/` 子文件夹中而不是项目的根文件夹中。在 github 页面上,他们说需要安装两个开发包(GraphicsMagic++ 和 WebP),然后使用 `make` 就能生成可执行文件。在截图中可以看到我已经将它们安装好了(在我读完相关的 Readme.md 文件后)。
|
||||
|
||||
因此,在编写 `snapcraft.yaml` 文件时已经有了四条腹稿:
|
||||
|
||||
1. `Makefile` 在 `src/` 子文件夹中而不是项目的根文件夹中。
|
||||
2. 这个程序编译时需要两个开发库。
|
||||
3. 为了让 timg 以 snap 包形式运行,我们需要将这两个库捆绑在 snap 包中(或者静态链接它们)。
|
||||
4. [timg][20] 是用 C++ 编写的,所以需要安装 g++。在编译之前,让我们通过 `snapcraft.yaml` 文件来检查 `build-essential` 元包是否已经安装。
|
||||
|
||||
### 从 snapcraft 开始
|
||||
|
||||
让我们新建一个名为 `timg-snap/` 的文件夹,并在其中运行 `snapcraft init` 这条命令来创建 `snapcraft.yaml` 工作的框架。
|
||||
|
||||
```
|
||||
ubuntu@snaps:~$ mkdir timg-snap
|
||||
ubuntu@snaps:~$ cd timg-snap/
|
||||
ubuntu@snaps:~/timg-snap$ snapcraft init
|
||||
Created snap/snapcraft.yaml.
|
||||
Edit the file to your liking or run `snapcraft` to get started
|
||||
ubuntu@snaps:~/timg-snap$ cat snap/snapcraft.yaml
|
||||
name: my-snap-name # you probably want to 'snapcraft register <name>'
|
||||
version: '0.1' # just for humans, typically '1.2+git' or '1.3.2'
|
||||
summary: Single-line elevator pitch for your amazing snap # 79 char long summary
|
||||
description: |
|
||||
This is my-snap's description. You have a paragraph or two to tell the most important story about your snap. Keep it under 100 words though, we live in tweetspace and your description wants to look good in the snap store.
|
||||
|
||||
grade: devel # must be 'stable' to release into candidate/stable channels
|
||||
confinement: devmode # use 'strict' once you have the right plugs and slots
|
||||
|
||||
parts:
|
||||
my-part:
|
||||
# See 'snapcraft plugins'
|
||||
plugin: nil
|
||||
```
|
||||
|
||||
### 填充元数据
|
||||
|
||||
`snapcraft.yaml` 配置文件的上半部分是元数据。我们需要一个一个把它们填满,这算是比较容易的部分。元数据由以下字段组成:
|
||||
|
||||
1. `name` (名字)—— snap 包的名字,它将公开在 Ubuntu 商店中。
|
||||
2. `version` (版本)—— snap 包的版本号。可以是源代码存储库中一个适当的分支或者标记,如果没有分支或标记的话,也可以是当前日期。
|
||||
3. `summary` (摘要)—— 不超过 80 个字符的简短描述。
|
||||
4. `description` (描述)—— 长一点的描述, 100 个字以下。
|
||||
5. `grade` (等级)—— `stable` (稳定)或者 `devel` (开发)。因为我们想要在 Ubuntu 商店的稳定通道中发布这个 snap 包,所以在 snap 包能正常工作后,就把它设置成 `stable`。
|
||||
6. `confinement` (限制)—— 我们首先设置为 `devmode` (开发模式),这样系统将不会以任何方式限制 snap 包。一旦它在 `devmode`下能正常工作,我们再考虑选择 `strict` (严格)还是 `classic` (传统)限制。
|
||||
|
||||
我们将使用 `timg` 这个名字:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ snapcraft register timg
|
||||
Registering timg.
|
||||
You already own the name 'timg'.
|
||||
```
|
||||
|
||||
是的,这个名字我已经注册了 :-)。
|
||||
|
||||
接下来,我们应该选择哪个版本的 timg?
|
||||
|
||||
![图片.png-72.7kB][21]
|
||||
|
||||
当在仓库中寻找分支或标记时,我们会发现有一个 v0.9.5 标签,其中有 2016 年 6 月 27 日最新提交的代码。
|
||||
|
||||
![图片.png-71.4kB][22]
|
||||
|
||||
然而主分支(`master`)中有两个看起来很重要的提交。因此我们使用主分支而不用 `v0.9.5` 标签的那个。我们使用今天的日期—— `20170226` 做为版本号。
|
||||
|
||||
我们从仓库中搜集了摘要和描述。其中摘要的内容为 `A terminal image viewer`,描述的内容为 `A viewer that uses 24-Bit color capabilities and unicode character blocks to display images in the terminal`。
|
||||
|
||||
最后,将 `grade` (等级)设置为 `stable` (稳定),将 `confinement` 限制设置为 `devmode` (开发模式)(一直到 snap 包真正起作用)。
|
||||
|
||||
这是更新后的 `snapcraft.yaml`,带有所有的元数据:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ cat snap/snapcraft.yaml
|
||||
name: timg
|
||||
version: '20170226'
|
||||
summary: A terminal image viewer
|
||||
description: |
|
||||
A viewer that uses 24-Bit color capabilities and unicode character blocks to display images in the terminal.
|
||||
|
||||
grade: stable
|
||||
confinement: devmode
|
||||
|
||||
parts:
|
||||
my-part:
|
||||
# See 'snapcraft plugins'
|
||||
plugin: nil
|
||||
```
|
||||
|
||||
### 弄清楚 `parts:` 是什么
|
||||
|
||||
现在我们需要将上面已经存在的 `parts:` 部分替换成真实的 `parts:`。
|
||||
|
||||
![timg-git-url.png-8kB][23]
|
||||
|
||||
*Git 仓库的 URL。*
|
||||
|
||||
![图片.png-28.7kB][24]
|
||||
|
||||
*存在 Makefile,因此我们需要 make 插件。*
|
||||
|
||||
我们已经知道 git 仓库的 URL 链接,并且 timg 源码中已有了 `Makefile` 文件。至于 [snapcraft make 插件][25] 的 Makefile 命令,正如文档所言,这个插件总是会运行 `make` 后再运行 `make install`。为了确认 `make` 插件的用法,我查看了 [snapcraft 可用插件列表][26]。
|
||||
|
||||
因此,我们将最初的配置:
|
||||
|
||||
```
|
||||
parts:
|
||||
my-part:
|
||||
# See 'snapcraft plugins'
|
||||
plugin: nil
|
||||
```
|
||||
|
||||
修改为:
|
||||
|
||||
```
|
||||
parts:
|
||||
timg:
|
||||
source: https://github.com/hzeller/timg.git
|
||||
plugin: make
|
||||
```
|
||||
|
||||
这是当前 `snapcraft.yaml` 文件的内容:
|
||||
|
||||
```
|
||||
name: timg
|
||||
version: '20170226'
|
||||
summary: A terminal image viewer
|
||||
description: |
|
||||
A viewer that uses 24-Bit color capabilities and unicode character blocks
|
||||
to display images in the terminal.
|
||||
|
||||
grade: stable
|
||||
confinement: devmode
|
||||
|
||||
parts:
|
||||
timg:
|
||||
source: https://github.com/hzeller/timg.git
|
||||
plugin: make
|
||||
```
|
||||
|
||||
让我们运行下 `snapcraft prime` 命令看看会发生什么:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ snapcraft prime
|
||||
Preparing to pull timg
|
||||
Pulling timg
|
||||
Cloning into '/home/ubuntu/timg-snap/parts/timg/src'...
|
||||
remote: Counting objects: 144, done.
|
||||
remote: Total 144 (delta 0), reused 0 (delta 0), pack-reused 144
|
||||
Receiving objects: 100% (144/144), 116.00 KiB | 0 bytes/s, done.
|
||||
Resolving deltas: 100% (89/89), done.
|
||||
Checking connectivity... done.
|
||||
Preparing to build timg
|
||||
Building timg
|
||||
make -j4
|
||||
make: *** No targets specified and no makefile found. Stop.
|
||||
Command '['/bin/sh', '/tmp/tmpem97fh9d', 'make', '-j4']' returned non-zero exit status 2
|
||||
ubuntu@snaps:~/timg-snap$
|
||||
```
|
||||
|
||||
我们可以看到 `snapcraft` 无法在源代码中找到 `Makefile` 文件,正如我们之前所暗示的,`Makefile` 位于 `src/` 子文件夹中。那么,我们可以让 `snapcraft` 使用 `src/` 文件夹中的 `Makefile` 文件吗?
|
||||
|
||||
每个 snapcraft 插件都有自己的选项,并且有一些通用选项是所有插件共享的。在本例中,我们希望研究那些[与源代码相关的 snapcraft 选项][27]。我们开始吧:
|
||||
|
||||
**source-subdir:path**
|
||||
|
||||
snapcraft 会<ruby>检出<rt>checkout</rt></ruby> `source` 关键字所引用的仓库或者解压归档文件到 `parts/<part-name>/src/` 中,但是它只会将特定的子目录复制到 `parts/<part-name>/build/` 中。
|
||||
|
||||
我们已经有了适当的选项,下面更新下 `parts`:
|
||||
|
||||
```
|
||||
parts:
|
||||
timg:
|
||||
source: https://github.com/hzeller/timg.git
|
||||
source-subdir: src
|
||||
plugin: make
|
||||
```
|
||||
|
||||
然后再次运行 `snapcraft prime`:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ snapcraft prime
|
||||
The 'pull' step of 'timg' is out of date:
|
||||
|
||||
The 'source-subdir' part property appears to have changed.
|
||||
|
||||
Please clean that part's 'pull' step in order to continue
|
||||
ubuntu@snaps:~/timg-snap$ snapcraft clean
|
||||
Cleaning up priming area
|
||||
Cleaning up staging area
|
||||
Cleaning up parts directory
|
||||
ubuntu@snaps:~/timg-snap$ snapcraft prime
|
||||
Skipping pull timg (already ran)
|
||||
Preparing to build timg
|
||||
Building timg
|
||||
make -j4
|
||||
g++ `GraphicsMagick++-config --cppflags --cxxflags` -Wall -O3 -fPIC -c -o timg.o timg.cc
|
||||
g++ -Wall -O3 -fPIC -c -o terminal-canvas.o terminal-canvas.cc
|
||||
/bin/sh: 1: GraphicsMagick++-config: not found
|
||||
timg.cc:33:22: fatal error: Magick++.h: No such file or directory
|
||||
compilation terminated.
|
||||
Makefile:10: recipe for target 'timg.o' failed
|
||||
make: *** [timg.o] Error 1
|
||||
make: *** Waiting for unfinished jobs....
|
||||
Command '['/bin/sh', '/tmp/tmpeeyxj5kw', 'make', '-j4']' returned non-zero exit status 2
|
||||
ubuntu@snaps:~/timg-snap$
|
||||
```
|
||||
|
||||
从错误信息我们可以得知 snapcraft 找不到 GraphicsMagick++ 这个开发库文件。根据 [snapcraft 常见关键字][29] 可知,我们需要在 `snapcraft.yaml` 中指定这个库文件,这样 snapcraft 才能安装它。
|
||||
|
||||
**build-packages:[deb, deb, deb…]**
|
||||
|
||||
列出构建 part 前需要在主机中安装的 Ubuntu 包。这些包通常不会进入最终的 snap 包中,除非它们含有 snap 包中二进制文件直接依赖的库文件(在这种情况下,可以通过 `ldd` 发现它们),或者在 `stage-package` 中显式地指定了它们。
|
||||
|
||||
让我们寻找下这个开发包的名字:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ apt-cache search graphicsmagick++ | grep dev
|
||||
graphicsmagick-libmagick-dev-compat/xenial 1.3.23-1build1 all
|
||||
libgraphicsmagick++1-dev/xenial 1.3.23-1build1 amd64
|
||||
format-independent image processing - C++ development files
|
||||
libgraphicsmagick1-dev/xenial 1.3.23-1build1 amd64
|
||||
format-independent image processing - C development files
|
||||
ubuntu@snaps:~/timg-snap$
|
||||
```
|
||||
|
||||
可以看到包名为 `libgraphicsmagick++1-dev`,下面是更新后的 `parts`:
|
||||
|
||||
```
|
||||
parts:
|
||||
timg:
|
||||
source: https://github.com/hzeller/timg.git
|
||||
source-subdir: src
|
||||
plugin: make
|
||||
build-packages:
|
||||
- libgraphicsmagick++1-dev
|
||||
```
|
||||
|
||||
再次运行 `snapcraft`:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ snapcraft
|
||||
Installing build dependencies: libgraphicsmagick++1-dev
|
||||
[...]
|
||||
The following NEW packages will be installed:
|
||||
libgraphicsmagick++-q16-12 libgraphicsmagick++1-dev libgraphicsmagick-q16-3
|
||||
libgraphicsmagick1-dev libwebp5
|
||||
[...]
|
||||
Building timg
|
||||
make -j4
|
||||
g++ `GraphicsMagick++-config --cppflags --cxxflags` -Wall -O3 -fPIC -c -o timg.o timg.cc
|
||||
g++ -Wall -O3 -fPIC -c -o terminal-canvas.o terminal-canvas.cc
|
||||
g++ -o timg timg.o terminal-canvas.o `GraphicsMagick++-config --ldflags --libs`
|
||||
/usr/bin/ld: cannot find -lwebp
|
||||
collect2: error: ld returned 1 exit status
|
||||
Makefile:7: recipe for target 'timg' failed
|
||||
make: *** [timg] Error 1
|
||||
Command '['/bin/sh', '/tmp/tmptma45jzl', 'make', '-j4']' returned non-zero exit status 2
|
||||
ubuntu@snaps:~/timg-snap$
|
||||
```
|
||||
|
||||
虽然只指定了开发库 `libgraphicsmagick+1-dev`,但 Ubuntu 还安装了一些代码库,包括 `libgraphicsmagick ++-q16-12`,以及动态代码库 `libwebp`。
|
||||
|
||||
这里仍然有一个错误,这个是因为缺少开发版本的 `webp` 库(一个静态库)。我们可以通过下面的命令找到它:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ apt-cache search libwebp | grep dev
|
||||
libwebp-dev - Lossy compression of digital photographic images.
|
||||
ubuntu@snaps:~/timg-snap$
|
||||
```
|
||||
|
||||
上面安装的 `libwebp5` 包只提供了一个动态库(.so)。通过 `libwebp-dev` 包,我们可以得到相应的静态库(.a)。好了,让我们更新下 `parts:` 部分:
|
||||
|
||||
```
|
||||
parts:
|
||||
timg:
|
||||
source: https://github.com/hzeller/timg.git
|
||||
source-subdir: src
|
||||
plugin: make
|
||||
build-packages:
|
||||
- libgraphicsmagick++1-dev
|
||||
- libwebp-dev
|
||||
```
|
||||
|
||||
下面是更新后的 `snapcraft.yaml` 文件的内容:
|
||||
|
||||
```
|
||||
name: timg
|
||||
version: '20170226'
|
||||
summary: A terminal image viewer
|
||||
description: |
|
||||
A viewer that uses 24-Bit color capabilities and unicode character blocks
|
||||
to display images in the terminal.
|
||||
|
||||
grade: stable
|
||||
confinement: devmode
|
||||
|
||||
parts:
|
||||
timg:
|
||||
source: https://github.com/hzeller/timg.git
|
||||
source-subdir: src
|
||||
plugin: make
|
||||
build-packages:
|
||||
- libgraphicsmagick++1-dev
|
||||
- libwebp-dev
|
||||
```
|
||||
|
||||
让我们运行下 `snapcraft prime`:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ snapcraft prime
|
||||
Skipping pull timg (already ran)
|
||||
Preparing to build timg
|
||||
Building timg
|
||||
make -j4
|
||||
g++ `GraphicsMagick++-config --cppflags --cxxflags` -Wall -O3 -fPIC -c -o timg.o timg.cc
|
||||
g++ -Wall -O3 -fPIC -c -o terminal-canvas.o terminal-canvas.cc
|
||||
g++ -o timg timg.o terminal-canvas.o `GraphicsMagick++-config --ldflags --libs`
|
||||
make install DESTDIR=/home/ubuntu/timg-snap/parts/timg/install
|
||||
install timg /usr/local/bin
|
||||
install: cannot create regular file '/usr/local/bin/timg': Permission denied
|
||||
Makefile:13: recipe for target 'install' failed
|
||||
make: *** [install] Error 1
|
||||
Command '['/bin/sh', '/tmp/tmptq_s1itc', 'make', 'install', 'DESTDIR=/home/ubuntu/timg-snap/parts/timg/install']' returned non-zero exit status 2
|
||||
ubuntu@snaps:~/timg-snap$
|
||||
```
|
||||
|
||||
我们遇到了一个新问题。由于 `Makefile` 文件是手工编写的,不符合 [snapcraft make 插件][30] 的参数设置,所以不能正确安装到 `prime/` 文件夹中。`Makefile` 会尝试安装到 `usr/local/bin` 中。
|
||||
|
||||
我们需要告诉 [snapcraft make 插件][31] 不要运行 `make install`,而是找到 `timg` 可执行文件然后把它放到 `prime/` 文件夹中。根据文档的描述:
|
||||
|
||||
```
|
||||
- artifacts:
|
||||
(列表)
|
||||
将 make 生成的指定文件复制或者链接到 snap 包安装目录。如果使用,则 `make install` 这步操作将被忽略。
|
||||
```
|
||||
|
||||
所以,我们需要将一些东西放到 `artifacts:` 中。但是具体是哪些东西?
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap/parts/timg$ ls build/src/
|
||||
Makefile terminal-canvas.h timg* timg.o
|
||||
terminal-canvas.cc terminal-canvas.o timg.cc
|
||||
ubuntu@snaps:~/timg-snap/parts/timg$
|
||||
```
|
||||
|
||||
在 `build/` 子目录中,我们可以找到 `make` 的输出结果。由于我们设置了 `source-subdir:` 为 `src`,所以 `artifacts:` 的基目录为 `build/src`。在这里我们可以找到可执行文件 `timg`,我们需要将它设置为 `artifacts:` 的一个参数。通过 `artifacts:`,我们可以把 `make` 输出的某些文件复制到 snap 包的安装目录(在 `prime/` 中)。
|
||||
|
||||
下面是更新后 `snapcraft.yaml` 文件 `parts:` 部分的内容:
|
||||
|
||||
```
|
||||
parts:
|
||||
timg:
|
||||
source: https://github.com/hzeller/timg.git
|
||||
source-subdir: src
|
||||
plugin: make
|
||||
build-packages:
|
||||
- libgraphicsmagick++1-dev
|
||||
- libwebp-dev
|
||||
artifacts: [timg]
|
||||
```
|
||||
|
||||
让我们运行 `snapcraft prime`:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ snapcraft prime
|
||||
Preparing to pull timg
|
||||
Pulling timg
|
||||
Cloning into '/home/ubuntu/timg-snap/parts/timg/src'...
|
||||
remote: Counting objects: 144, done.
|
||||
remote: Total 144 (delta 0), reused 0 (delta 0), pack-reused 144
|
||||
Receiving objects: 100% (144/144), 116.00 KiB | 207.00 KiB/s, done.
|
||||
Resolving deltas: 100% (89/89), done.
|
||||
Checking connectivity... done.
|
||||
Preparing to build timg
|
||||
Building timg
|
||||
make -j4
|
||||
g++ `GraphicsMagick++-config --cppflags --cxxflags` -Wall -O3 -fPIC -c -o timg.o timg.cc
|
||||
g++ -Wall -O3 -fPIC -c -o terminal-canvas.o terminal-canvas.cc
|
||||
g++ -o timg timg.o terminal-canvas.o `GraphicsMagick++-config --ldflags --libs`
|
||||
Staging timg
|
||||
Priming timg
|
||||
ubuntu@snaps:~/timg-snap$
|
||||
```
|
||||
|
||||
我们还将继续迭代。
|
||||
|
||||
### 导出命令
|
||||
|
||||
到目前为止,snapcraft 生成了可执行文件,但没有导出给用户使用的命令。接下来我们需要通过 `apps:` 导出一个命令。
|
||||
|
||||
首先我们需要知道命令在 `prime/` 的哪个子文件夹中:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ ls prime/
|
||||
meta/ snap/ timg* usr/
|
||||
ubuntu@snaps:~/timg-snap$
|
||||
```
|
||||
|
||||
它在 `prime/` 子文件夹的根目录中。现在,我们已经准备好要在 `snapcaft.yaml` 中增加 `apps:` 的内容:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ cat snap/snapcraft.yaml
|
||||
name: timg
|
||||
version: '20170226'
|
||||
summary: A terminal image viewer
|
||||
description: |
|
||||
A viewer that uses 24-Bit color capabilities and unicode character blocks
|
||||
to display images in the terminal.
|
||||
|
||||
grade: stable
|
||||
confinement: devmode
|
||||
|
||||
apps:
|
||||
timg:
|
||||
command: timg
|
||||
|
||||
parts:
|
||||
timg:
|
||||
source: https://github.com/hzeller/timg.git
|
||||
source-subdir: src
|
||||
plugin: make
|
||||
build-packages:
|
||||
- libgraphicsmagick++1-dev
|
||||
- libwebp-dev
|
||||
artifacts: [timg]
|
||||
```
|
||||
|
||||
让我们再次运行 `snapcraft prime`,然后测试下生成的 snap 包:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ snapcraft prime
|
||||
Skipping pull timg (already ran)
|
||||
Skipping build timg (already ran)
|
||||
Skipping stage timg (already ran)
|
||||
Skipping prime timg (already ran)
|
||||
ubuntu@snaps:~/timg-snap$ snap try --devmode prime/
|
||||
timg 20170226 mounted from /home/ubuntu/timg-snap/prime
|
||||
ubuntu@snaps:~/timg-snap$
|
||||
```
|
||||
|
||||
![图片.png-42.3kB][32]
|
||||
|
||||
*图片来源: https://www.flickr.com/photos/mustangjoe/6091603784/*
|
||||
|
||||
我们可以通过 `snap try --devmode prime/ ` 启用该 snap 包然后测试 `timg` 命令。这是一种高效的测试方法,可以避免生成 .snap 文件,并且无需安装和卸载它们,因为 `snap try prime/` 直接使用了 `prime/` 文件夹中的内容。
|
||||
|
||||
### 限制 snap
|
||||
|
||||
到目前为止,snap 包一直是在不受限制的开发模式下运行的。让我们看看如何限制它的运行:
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ snap list
|
||||
Name Version Rev Developer Notes
|
||||
core 16-2 1337 canonical -
|
||||
timg 20170226 x1 devmode,try
|
||||
ubuntu@snaps:~/timg-snap$ snap try --jailmode prime
|
||||
timg 20170226 mounted from /home/ubuntu/timg-snap/prime
|
||||
ubuntu@snaps:~/timg-snap$ snap list
|
||||
Name Version Rev Developer Notes
|
||||
core 16-2 1337 canonical -
|
||||
timg 20170226 x2 jailmode,try
|
||||
ubuntu@snaps:~/timg-snap$ timg pexels-photo-149813.jpeg
|
||||
Trouble loading pexels-photo-149813.jpeg (Magick: Unable to open file (pexels-photo-149813.jpeg) reported by magick/blob.c:2828 (OpenBlob))
|
||||
ubuntu@snaps:~/timg-snap$
|
||||
```
|
||||
|
||||
通过这种方式,我们可以无需修改 `snapcraft.yaml` 文件就从开发模式(`devmode`)切换到限制模式(`jailmode`)(`confinement: strict`)。正如预期的那样,`timg` 无法读取图像,因为我们没有开放访问文件系统的权限。
|
||||
|
||||
现在,我们需要作出决定。使用限制模式,我们可以很容易授予某个命令访问用户 `$HOME` 目录中文件的权限,但是只能访问那里。如果图像文件位于其它地方,我们总是需要复制到 `$HOME` 目录并在 `$HOME` 的副本上运行 timg。如果我们觉得可行,那我们可以设置 `snapcraf.yaml` 为:
|
||||
|
||||
```
|
||||
name: timg
|
||||
version: '20170226'
|
||||
summary: A terminal image viewer
|
||||
description: |
|
||||
A viewer that uses 24-Bit color capabilities and unicode character blocks
|
||||
to display images in the terminal.
|
||||
|
||||
grade: stable
|
||||
confinement: strict
|
||||
|
||||
apps:
|
||||
timg:
|
||||
command: timg
|
||||
plugs: [home]
|
||||
|
||||
parts:
|
||||
timg:
|
||||
source: https://github.com/hzeller/timg.git
|
||||
source-subdir: src
|
||||
plugin: make
|
||||
build-packages:
|
||||
- libgraphicsmagick++1-dev
|
||||
- libwebp-dev
|
||||
artifacts: [timg]
|
||||
```
|
||||
|
||||
另一方面,如果希望 timg snap 包能访问整个文件系统,我们可以设置传统限制来实现。对应的 `snapcraft.yaml` 内容如下:
|
||||
|
||||
```
|
||||
name: timg
|
||||
version: '20170226'
|
||||
summary: A terminal image viewer
|
||||
description: |
|
||||
A viewer that uses 24-Bit color capabilities and unicode character blocks
|
||||
to display images in the terminal.
|
||||
|
||||
grade: stable
|
||||
confinement: classic
|
||||
|
||||
apps:
|
||||
timg:
|
||||
command: timg
|
||||
|
||||
parts:
|
||||
timg:
|
||||
source: https://github.com/hzeller/timg.git
|
||||
source-subdir: src
|
||||
plugin: make
|
||||
build-packages:
|
||||
- libgraphicsmagick++1-dev
|
||||
- libwebp-dev
|
||||
artifacts: [timg]
|
||||
```
|
||||
|
||||
接下来我们将选择严格(`strict`)约束选项。因此,图像应该只能放在 $HOME 中。
|
||||
|
||||
### 打包和测试
|
||||
|
||||
让我们打包这个 snap,也就是制作 .snap 文件,然后在新安装的 Ubuntu 系统上对它进行测试。
|
||||
|
||||
```
|
||||
ubuntu@snaps:~/timg-snap$ snapcraft
|
||||
Skipping pull timg (already ran)
|
||||
Skipping build timg (already ran)
|
||||
Skipping stage timg (already ran)
|
||||
Skipping prime timg (already ran)
|
||||
Snapping 'timg' \
|
||||
Snapped timg_20170226_amd64.snap
|
||||
ubuntu@snaps:~/timg-snap$
|
||||
```
|
||||
|
||||
我们如何在几秒钟内得到一个全新安装的 Ubuntu 系统来对 snap 包进行测试?
|
||||
|
||||
请查看 [尝试在 Ubuntu 上使用 LXD 容器][33],并在你的系统上设置 LXD。然后回到这里,尝试运行下面的命令:
|
||||
|
||||
```
|
||||
$ lxc launch ubuntu:x snaptesting
|
||||
Creating snaptesting
|
||||
Starting snaptesting
|
||||
$ lxc file push timg_20170226_amd64.snap snaptesting/home/ubuntu/
|
||||
$ lxc exec snaptesting -- sudo su - ubuntu
|
||||
To run a command as administrator (user "root"), use "sudo <command>".
|
||||
See "man sudo_root" for details.
|
||||
|
||||
ubuntu@snaptesting:~$ ls
|
||||
timg_20170226_amd64.snap
|
||||
ubuntu@snaptesting:~$ snap install timg_20170226_amd64.snap
|
||||
error: access denied (try with sudo)
|
||||
ubuntu@snaptesting:~$ sudo snap install timg_20170226_amd64.snap
|
||||
error: cannot find signatures with metadata for snap "timg_20170226_amd64.snap"
|
||||
ubuntu@snaptesting:~$ sudo snap install timg_20170226_amd64.snap --dangerous
|
||||
error: cannot perform the following tasks:
|
||||
- Mount snap "core" (1337) ([start snap-core-1337.mount] failed with exit status 1: Job for snap-core-1337.mount failed. See "systemctl status snap-core-1337.mount" and "journalctl -xe" for details.
|
||||
)
|
||||
ubuntu@snaptesting:~$ sudo apt install squashfuse
|
||||
[...]
|
||||
Setting up squashfuse (0.1.100-0ubuntu1~ubuntu16.04.1) ...
|
||||
ubuntu@snaptesting:~$ sudo snap install timg_20170226_amd64.snap --dangerous
|
||||
timg 20170226 installed
|
||||
ubuntu@snaptesting:~$ wget https://farm7.staticflickr.com/6187/6091603784_d6960c8be2_z_d.jpg
|
||||
[...]
|
||||
2017-02-26 22:12:18 (636 KB/s) - ‘6091603784_d6960c8be2_z_d.jpg’ saved [240886/240886]
|
||||
ubuntu@snaptesting:~$ timg 6091603784_d6960c8be2_z_d.jpg
|
||||
[it worked!]
|
||||
ubuntu@snaptesting:~$
|
||||
```
|
||||
|
||||
我们启动了一个名为 `snaptesting` 的 LXD 容器,并将 .snap 文件复制进去。然后,通过普通用户连接到容器,并尝试安装 snap 包。最初,我们安装失败了,因为在无特权的 LXD 容器中安装 snap 包需要使用 `sudo` 。接着又失败了,因为 .snap 没有经过签名(我们需要使用 `--dangerous` 参数)。然而还是失败了,这次是因为我们需要安装 `squashfuse` 包(Ubuntu 16.04 镜像中没有预装)。最后,我们成功安装了snap,并设法查看了图像。
|
||||
|
||||
在一个全新安装的 Linux 系统中测试 snap 包是很重要的,因为这样才能确保 snap 包中包含所有必须的代码库。在这个例子中,我们使用了静态库并运行良好。
|
||||
|
||||
### 发布到 Ubuntu 商店
|
||||
|
||||
这是 [发布 snap 包到 Ubuntu 商店的说明][34]。 在之前的教程中,我们已经发布了一些 snap 包。对于 `timg` 来说,我们设置了严格限制和稳定等级。因此,我们会将它发布到稳定通道。
|
||||
|
||||
```
|
||||
$ snapcraft push timg_20170226_amd64.snap
|
||||
Pushing 'timg_20170226_amd64.snap' to the store.
|
||||
Uploading timg_20170226_amd64.snap [ ] 0%
|
||||
Uploading timg_20170226_amd64.snap [=======================================] 100%
|
||||
Ready to release!|
|
||||
Revision 6 of 'timg' created.
|
||||
$ snapcraft release timg 6 stable
|
||||
Track Arch Series Channel Version Revision
|
||||
latest amd64 16 stable 20170226 6
|
||||
candidate ^ ^
|
||||
beta 0.9.5 5
|
||||
edge 0.9.5 5
|
||||
The 'stable' channel is now open.
|
||||
```
|
||||
|
||||
我们把 .snap 包推送到 Ubuntu 商店后,得到了一个 `Revision 6`。然后,我们将 timg `Revision 6` 发布到了 Ubuntu 商店的稳定通道。
|
||||
|
||||
在候选通道中没有已发布的 snap 包,它继承的是稳定通道的包,所以显示 `^` 字符。
|
||||
|
||||
在之前的测试中,我将一些较老版本的 snap 包上传到了测试和边缘通道。这些旧版本使用了 timg 标签为 `0.9.5` 的源代码。
|
||||
|
||||
我们可以通过将稳定版本发布到测试和边缘通道来移除旧的 0.9.5 版本的包。
|
||||
|
||||
```
|
||||
$ snapcraft release timg 6 beta
|
||||
Track Arch Series Channel Version Revision
|
||||
latest amd64 16 stable 20170226 6
|
||||
candidate ^ ^
|
||||
beta 20170226 6
|
||||
edge 0.9.5 5
|
||||
$ snapcraft release timg 6 edge
|
||||
Track Arch Series Channel Version Revision
|
||||
latest amd64 16 stable 20170226 6
|
||||
candidate ^ ^
|
||||
beta 20170226 6
|
||||
edge 20170226 6
|
||||
```
|
||||
|
||||
### 使用 timg
|
||||
|
||||
让我们不带参数运行 `timg`:
|
||||
|
||||
```
|
||||
ubuntu@snaptesting:~$ timg
|
||||
Expected image filename.
|
||||
usage: /snap/timg/x1/timg [options] <image> [<image>...]
|
||||
Options:
|
||||
-g<w>x<h> : Output pixel geometry. Default from terminal 80x48
|
||||
-s[<ms>] : Scroll horizontally (optionally: delay ms (60)).
|
||||
-d<dx:dy> : delta x and delta y when scrolling (default: 1:0).
|
||||
-w<seconds>: If multiple images given: Wait time between (default: 0.0).
|
||||
-t<seconds>: Only animation or scrolling: stop after this time.
|
||||
-c<num> : Only Animation or scrolling: number of runs through a full cycle.
|
||||
-C : Clear screen before showing image.
|
||||
-F : Print filename before showing picture.
|
||||
-v : Print version and exit.
|
||||
If both -c and -t are given, whatever comes first stops.
|
||||
If both -w and -t are given for some animation/scroll, -t takes precedence
|
||||
ubuntu@snaptesting:~$
|
||||
```
|
||||
|
||||
这里提到当前我们终端模拟器的缩放级别,即分辨率为:80 × 48。
|
||||
|
||||
让我们缩小一点,并最大化 GNOME 终端窗口。
|
||||
|
||||
```
|
||||
-g<w>x<h> : Output pixel geometry. Default from terminal 635x428
|
||||
```
|
||||
|
||||
这是一个更好的解决方案,但我几乎看不到字符,因为他们太小了。让我们调用前面的命令再次显示这辆车。
|
||||
|
||||
![图片.png-904.9kB][35]
|
||||
|
||||
你所看到的是调整后的图像(1080p)。虽然它是用彩色文本字符显示的,但看起来依旧很棒。
|
||||
|
||||
接下来呢?`timg` 其实也可以播放 gif 动画哦!
|
||||
|
||||
```
|
||||
$ wget https://m.popkey.co/9b7141/QbAV_f-maxage-0.gif -O JonahHillAmazed.gif$ timg JonahHillAmazed.gif
|
||||
```
|
||||
|
||||
你可以试着安装 `timg` 来体验 gif 动画。要是不想自己动手,可以在 [asciinema][36] 上查看相关记录 (如果视频看上去起伏不定的,请重新运行它)。
|
||||
|
||||
谢谢阅读!
|
||||
|
||||
-----
|
||||
译者简介:
|
||||
|
||||
经常混迹于 snapcraft.io,对 Ubuntu Core、Snaps 和 Snapcraft 有着浓厚的兴趣,并致力于将这些还在快速发展的新技术通过翻译或原创的方式介绍到中文世界。有兴趣的小伙伴也可以关注译者个人公众号: `Snapcraft`
|
||||
|
||||
-----
|
||||
via:https://blog.simos.info/how-to-create-a-snap-for-timg-with-snapcraft-on-ubuntu/
|
||||
|
||||
作者:[Mi blog lah!][37]
|
||||
译者:[Snapcrafter](https://github.com/Snapcrafter)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
|
||||
[1]: https://github.com/hzeller/timg
|
||||
[2]: https://tutorials.ubuntu.com/tutorial/create-your-first-snap
|
||||
[3]: https://github.com/hzeller/timg
|
||||
[4]: https://snapcraft.io/docs/reference/plugins/make
|
||||
[5]:https://github.com/hzeller/timg
|
||||
[6]: http://static.zybuluo.com/apollomoon/ynm5k5urc7idb037ahca2s93/%E5%9B%BE%E7%89%87.png
|
||||
[7]: http://static.zybuluo.com/apollomoon/h2ynj68axdqiy7dwszgw5z1f/%E5%9B%BE%E7%89%87.png
|
||||
[8]:https://gist.github.com/XVilka/8346728
|
||||
[9]:https://en.wikipedia.org/wiki/Escape_sequence
|
||||
[10]:https://github.com/hzeller/timg
|
||||
[11]: http://static.zybuluo.com/apollomoon/nzlqpq3xn4rs72h4r96k4xlw/%E5%9B%BE%E7%89%87.png
|
||||
[12]:http://design.ubuntu.com/wp-content/uploads/ubuntu-logo112.png
|
||||
[13]: http://static.zybuluo.com/apollomoon/vo1nxnu4xfaghyib03fnkvq4/%E5%9B%BE%E7%89%87.png
|
||||
[14]:https://www.flickr.com/photos/doug88888/5776072628/in/photolist-9WCiNQ-7U3Trc-7YUZBL-5DwkEQ-6e1iT8-a372aS-5F75aL-a1gbow-6eNayj-8gWK2H-5CtH7P-6jVqZv-86RpwN-a2nEnB-aiRmsc-6aKvwK-8hmXrN-5CWDNP-62hWM8-a9smn1-ahQqHw-a22p3w-a36csK-ahN4Pv-7VEmnt-ahMSiT-9NpTa7-5A3Pon-ai7DL7-9TKCqV-ahr7gN-a1boqP-83ZzpH-9Sqjmq-5xujdi-7UmDVb-6J2zQR-5wAGNR-5eERar-5KVDym-5dL8SZ-5S2Uut-7RVyHg-9Z6MAt-aiRiT4-5tLesw-aGLSv6-5ftp6j-5wAVBq-5T2KAP
|
||||
[15]: https://github.com/hzeller/timg
|
||||
[16]: https://github.com/hzeller/timg
|
||||
[17]:https://github.com/hzeller/timg
|
||||
[18]: https://github.com/hzeller/timg
|
||||
[19]: http://static.zybuluo.com/apollomoon/hovu73yqx08pdhm8qmdg6f6a/%E5%9B%BE%E7%89%87.png
|
||||
[20]:https://github.com/hzeller/timg
|
||||
[21]: http://static.zybuluo.com/apollomoon/o64i7jm65u3o12wg3fqqcn7x/%E5%9B%BE%E7%89%87.png
|
||||
[22]: http://static.zybuluo.com/apollomoon/t4w1uak9j4h6rfn4ghc8q15k/%E5%9B%BE%E7%89%87.png
|
||||
[23]: http://static.zybuluo.com/apollomoon/cvuetj2rzd5nee7pgfcp7wr3/timg-git-url.png
|
||||
[24]: http://static.zybuluo.com/apollomoon/dxtl628r1qavphhzu70jiw1n/%E5%9B%BE%E7%89%87.png
|
||||
[25]: https://snapcraft.io/docs/reference/plugins/make
|
||||
[26]:https://snapcraft.io/docs/reference/plugins/
|
||||
[27]: https://snapcraft.io/docs/reference/plugins/source
|
||||
[28]: https://snapcraft.io/docs/reference/plugins/source
|
||||
[29]:https://snapcraft.io/docs/reference/plugins/common
|
||||
[30]:https://snapcraft.io/docs/reference/plugins/make
|
||||
[31]:https://snapcraft.io/docs/reference/plugins/make
|
||||
[32]: http://static.zybuluo.com/apollomoon/v9y3vutt8li4wwaxeigwr4yz/%E5%9B%BE%E7%89%87.png
|
||||
[33]:https://blog.simos.info/trying-out-lxd-containers-on-our-ubuntu/
|
||||
[34]:https://snapcraft.io/docs/build-snaps/publish
|
||||
[35]: http://static.zybuluo.com/apollomoon/clnv44g3bwhaqog7o1jpvpcd/%E5%9B%BE%E7%89%87.png
|
||||
[36]: https://asciinema.org/a/dezbe2gpye84e0pjndp8t0pvh
|
||||
[37]: https://blog.simos.info/
|
||||
[38]:https://snapcraft.io/docs/reference/confinement
|
||||
@@ -0,0 +1,66 @@
|
||||
为什么 DevOps 如我们所知道的那样,是安全的终结
|
||||
==========
|
||||
|
||||

|
||||
|
||||
安全难以推行。在企业管理者迫使开发团队尽快发布程序的大环境下,很难说服他们花费有限的时间来修补安全漏洞。但是鉴于所有网络攻击中有 84% 发生在应用层,作为一个组织是无法承担其开发团队不包括安全性带来的后果。
|
||||
|
||||
DevOps 的崛起为许多安全负责人带来了困境。Sonatype 的前 CTO [Josh Corman][2] 说:“这是对安全的威胁,但这也是让安全变得更好的机会。” Corman 是一个坚定的[将安全和 DevOps 实践整合起来创建 “坚固的 DevOps”][3]的倡导者。_Business Insights_ 与 Corman 谈论了安全和 DevOps 共同的价值,以及这些共同价值如何帮助组织更少地受到中断和攻击的影响。
|
||||
|
||||
### 安全和 DevOps 实践如何互惠互利?
|
||||
|
||||
**Josh Corman:** 一个主要的例子是 DevOps 团队对所有可测量的东西进行检测的倾向。安全性一直在寻找更多的情报和遥测。你可以获取许多 DevOps 团队正在测量的信息,并将这些信息输入到你的日志管理或 SIEM (安全信息和事件管理系统)。
|
||||
|
||||
一个 OODA 循环(<ruby>观察<rt>observe</rt></ruby>、<ruby>定向<rt>orient</rt></ruby>、<ruby>决定<rt>decide</rt></ruby>、<ruby>行为<rt>act</rt></ruby>)的前提是有足够普遍的眼睛和耳朵,以注意到窃窃私语和回声。DevOps 为你提供无处不在的仪器。
|
||||
|
||||
### 他们有分享其他文化观点吗?
|
||||
|
||||
**JC:** “严肃对待你的代码”是一个共同的价值观。例如,由 Netflix 编写的软件工具 Chaos Monkey 是 DevOps 团队的分水岭。它是为了测试亚马逊网络服务的弹性和可恢复性而创建的,Chaos Monkey 使得 Netflix 团队更加强大,更容易为中断做好准备。
|
||||
|
||||
所以现在有个想法是我们的系统需要测试,因此,James Wickett 和我及其他人决定做一个邪恶的、带有攻击性的 Chaos Monkey,这就是 GAUNTLT 项目的来由。它基本上是一堆安全测试, 可以在 DevOps 周期和 DevOps 工具链中使用。它也有非常适合 DevOps 的API。
|
||||
|
||||
### 企业安全和 DevOps 价值在哪里相交?
|
||||
|
||||
**JC:** 这两个团队都认为复杂性是一切事情的敌人。例如,[安全人员和 Rugged DevOps 人员][4]实际上可以说:“看,我们在我们的项目中使用了 11 个日志框架 - 也许我们不需要那么多,也许攻击面和复杂性可能会让我们受到伤害或者损害产品的质量或可用性。”
|
||||
|
||||
复杂性往往是许多事情的敌人。通常情况下,你不会很难说服 DevOps 团队在架构层面使用更好的建筑材料:使用最新的、最不易受攻击的版本,并使用较少的组件。
|
||||
|
||||
### “更好的建筑材料”是什么意思?
|
||||
|
||||
**JC:** 我是世界上最大的开源仓库的保管人,所以我能看到他们在使用哪些版本,里面有哪些漏洞,何时他们没有修复漏洞,以及等了多久。例如,某些日志记录框架从不会修复任何错误。其中一些会在 90 天内修复了大部分的安全漏洞。人们越来越多地遭到攻击,因为他们使用了一个毫无安全的框架。
|
||||
|
||||
除此之外,即使你不知道日志框架的质量,拥有 11 个不同的框架会变得非常笨重、出现 bug,还有额外的工作和复杂性。你暴露在漏洞中的风险是非常大的。你想把时间花在修复大量的缺陷上,还是在制造下一个大的破坏性的事情上?
|
||||
|
||||
[Rugged DevOps 的关键是软件供应链管理][5],其中包含三个原则:使用更少和更好的供应商、使用这些供应商的最高质量的部分、并跟踪这些部分,以便在发生错误时,你可以有一个及时和敏捷的响应。
|
||||
|
||||
### 所以变更管理也很重要。
|
||||
|
||||
**JC:** 是的,这是另一个共同的价值。我发现,当一家公司想要执行诸如异常检测或净流量分析等安全测试时,他们需要知道“正常”的样子。让人们失误的许多基本事情与仓库和补丁管理有关。
|
||||
|
||||
我在 _Verizon 数据泄露调查报告_中看到,追踪去年被成功利用的漏洞后,其中 97% 归结为 10 个 CVE(常见漏洞和风险),而这 10 个已经被修复了十多年。所以,我们羞于谈论高级间谍活动。我们没有做基本的补丁工作。现在,我不是说如果你修复这 10 个CVE,那么你就没有被利用,而是这占据了人们实际失误的最大份额。
|
||||
|
||||
[DevOps 自动化工具][6]的好处是它们已经成为一个意外的变更管理数据库。其真实反应了谁在哪里什么时候做了变更。这是一个巨大的胜利,因为我们经常对安全性有最大影响的因素无法控制。你承受了 CIO 和 CTO 做出的选择的后果。随着 IT 通过自动化变得更加严格和可重复,你可以减少人为错误的机会,并且哪里发生了变化更加可追溯。
|
||||
|
||||
### 你认为什么是最重要的共同价值?
|
||||
|
||||
**JC:** DevOps 涉及到过程和工具链,但我认为定义这种属性的是文化,特别是同感。 DevOps 有用是因为开发人员和运维团队能够更好地了解彼此,并做出更明智的决策。不是在解决孤岛中的问题,而是为了活动流程和目标解决。如果你向 DevOps 的团队展示安全如何能使他们变得更好,那么作为回馈他们往往会问:“那么,我们是否有任何选择让你的生活更轻松?”因为他们通常不知道他们做的 X、Y 或 Z 的选择使它无法包含安全性。
|
||||
|
||||
对于安全团队,驱动价值的方法之一是在寻求帮助之前变得更有所帮助,在我们告诉 DevOps 团队要做什么之前提供定性和定量的价值。你必须获得 DevOps 团队的信任,并获得发挥的权利,然后才能得到回报。它通常比你想象的快很多。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://techbeacon.com/why-devops-end-security-we-know-it
|
||||
|
||||
作者:[Mike Barton][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://twitter.com/intent/follow?original_referer=https%3A%2F%2Ftechbeacon.com%2Fwhy-devops-end-security-we-know-it%3Fimm_mid%3D0ee8c5%26cmp%3Dem-webops-na-na-newsltr_20170310&ref_src=twsrc%5Etfw®ion=follow_link&screen_name=mikebarton&tw_p=followbutton
|
||||
[1]:https://techbeacon.com/resources/application-security-devops-true-state?utm_source=tb&utm_medium=article&utm_campaign=inline-cta
|
||||
[2]:https://twitter.com/joshcorman
|
||||
[3]:https://techbeacon.com/want-rugged-devops-team-your-release-security-engineers
|
||||
[4]:https://techbeacon.com/rugged-devops-rsa-6-takeaways-security-ops-pros
|
||||
[5]:https://techbeacon.com/josh-corman-security-devops-how-shared-team-values-can-reduce-threats
|
||||
[6]:https://techbeacon.com/devops-automation-best-practices-how-much-too-much
|
||||
568
published/201710/20170312 OpenGL Go Tutorial Part 1.md
Normal file
568
published/201710/20170312 OpenGL Go Tutorial Part 1.md
Normal file
@@ -0,0 +1,568 @@
|
||||
OpenGL 与 Go 教程(一)Hello, OpenGL
|
||||
============================================================
|
||||
|
||||
- [第一节: Hello, OpenGL][6]
|
||||
- [第二节: 绘制游戏面板][7]
|
||||
- [第三节: 实现游戏功能][8]
|
||||
|
||||
这篇教程的所有源代码都可以在 [GitHub][9] 上找到。
|
||||
|
||||
### 介绍
|
||||
|
||||
[OpenGL][19] 是一门相当好的技术,适用于从桌面的 GUI 到游戏,到移动应用甚至 web 应用的多种类型的绘图工作。我敢保证,你今天看到的图形有些就是用 OpenGL 渲染的。可是,不管 OpenGL 多受欢迎、有多好用,与学习其它高级绘图库相比,学习 OpenGL 是要相当足够的决心的。
|
||||
|
||||
这个教程的目的是给你一个切入点,让你对 OpenGL 有个基本的了解,然后教你怎么用 [Go][20] 操作它。几乎每种编程语言都有绑定 OpenGL 的库,Go 也不例外,它有 [go-gl][21] 这个包。这是一个完整的套件,可以绑定 OpenGL ,适用于多种版本的 OpenGL。
|
||||
|
||||
这篇教程会按照下面列出的几个阶段进行介绍,我们最终的目标是用 OpenGL 在桌面窗口绘制游戏面板,进而实现[康威生命游戏][22]。完整的源代码可以在 GitHub [github.com/KyleBanks/conways-gol][23] 上获得,当你有疑惑的时候可以随时查看源代码,或者你要按照自己的方式学习也可以参考这个代码。
|
||||
|
||||
在我们开始之前,我们要先弄明白<ruby>康威生命游戏<rt>Conway's Game of Life</rt></ruby> 到底是什么。这里是 [Wikipedia][24] 上面的总结:
|
||||
|
||||
> 《生命游戏》,也可以简称为 Life,是一个细胞自动变化的过程,由英国数学家 John Horton Conway 于 1970 年提出。
|
||||
>
|
||||
> 这个“游戏”没有玩家,也就是说它的发展依靠的是它的初始状态,不需要输入。用户通过创建初始配置文件、观察它如何演变,或者对于高级“玩家”可以创建特殊属性的模式,进而与《生命游戏》进行交互。
|
||||
>
|
||||
> `规则`
|
||||
>
|
||||
> 《生命游戏》的世界是一个无穷多的二维正交的正方形细胞的格子世界,每一个格子都有两种可能的状态,“存活”或者“死亡”,也可以说是“填充态”或“未填充态”(区别可能很小,可以把它看作一个模拟人类/哺乳动物行为的早期模型,这要看一个人是如何看待方格里的空白)。每一个细胞与它周围的八个细胞相关联,这八个细胞分别是水平、垂直、斜对角相接的。在游戏中的每一步,下列事情中的一件将会发生:
|
||||
>
|
||||
> 1. 当任何一个存活的细胞的附近少于 2 个存活的细胞时,该细胞将会消亡,就像人口过少所导致的结果一样
|
||||
> 2. 当任何一个存活的细胞的附近有 2 至 3 个存活的细胞时,该细胞在下一代中仍然存活。
|
||||
> 3. 当任何一个存活的细胞的附近多于 3 个存活的细胞时,该细胞将会消亡,就像人口过多所导致的结果一样
|
||||
> 4. 任何一个消亡的细胞附近刚好有 3 个存活的细胞,该细胞会变为存活的状态,就像重生一样。
|
||||
|
||||
不需要其他工具,这里有一个我们将会制作的演示程序:
|
||||
|
||||

|
||||
|
||||
在我们的运行过程中,白色的细胞表示它是存活着的,黑色的细胞表示它已经死亡。
|
||||
|
||||
### 概述
|
||||
|
||||
本教程将会涉及到很多基础内容,从最基本的开始,但是你还是要对 Go 由一些最基本的了解 —— 至少你应该知道变量、切片、函数和结构体,并且装了一个 Go 的运行环境。我写这篇教程用的 Go 版本是 1.8,但它应该与之前的版本兼容。这里用 Go 语言实现没有什么特别新奇的东西,因此只要你有过类似的编程经历就行。
|
||||
|
||||
这里是我们在这个教程里将会讲到的东西:
|
||||
|
||||
* [第一节: Hello, OpenGL][10]: 安装 OpenGL 和 [GLFW][11],在窗口上绘制一个三角形。
|
||||
* [第二节: 绘制游戏面板][12]: 用三角形拼成方形,在窗口上用方形绘成格子。
|
||||
* [第三节: 实现游戏功能][13]: 实现 Conway 游戏
|
||||
|
||||
最后的源代码可以在 [GitHub][25] 上获得,每一节的末尾有个_回顾_,包含该节相关的代码。如果有什么不清楚的地方或者是你感到疑惑的,看看每一节末尾的完整代码。
|
||||
|
||||
现在就开始吧!
|
||||
|
||||
### 安装 OpenGL 和 GLFW
|
||||
|
||||
我们介绍过 OpenGL,但是为了使用它,我们要有个窗口可以绘制东西。 [GLFW][26] 是一款用于 OpenGL 的跨平台 API,允许我们创建并使用窗口,而且它也是 [go-gl][27] 套件中提供的。
|
||||
|
||||
我们要做的第一件事就是确定 OpenGL 的版本。为了方便本教程,我们将会使用 `OpenGL v4.1`,但要是你的操作系统不支持最新的 OpenGL,你也可以用 `v2.1`。要安装 OpenGL,我们需要做这些事:
|
||||
|
||||
```
|
||||
# 对于 OpenGL 4.1
|
||||
$ go get github.com/go-gl/gl/v4.1-core/gl
|
||||
|
||||
# 或者 2.1
|
||||
$ go get github.com/go-gl/gl/v2.1/gl
|
||||
```
|
||||
|
||||
然后是安装 GLFW:
|
||||
|
||||
```
|
||||
$ go get github.com/go-gl/glfw/v3.2/glfw
|
||||
```
|
||||
|
||||
安装好这两个包之后,我们就可以开始了!先创建 `main.go` 文件,导入相应的包(我们待会儿会用到的其它东西)。
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-gl/gl/v4.1-core/gl" // OR: github.com/go-gl/gl/v2.1/gl
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
)
|
||||
```
|
||||
|
||||
接下来定义一个叫做 `main` 的函数,这是用来初始化 OpenGL 以及 GLFW,并显示窗口的:
|
||||
|
||||
```
|
||||
const (
|
||||
width = 500
|
||||
height = 500
|
||||
)
|
||||
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
window := initGlfw()
|
||||
defer glfw.Terminate()
|
||||
|
||||
for !window.ShouldClose() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
// initGlfw 初始化 glfw 并且返回一个可用的窗口。
|
||||
func initGlfw() *glfw.Window {
|
||||
if err := glfw.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
glfw.WindowHint(glfw.Resizable, glfw.False)
|
||||
glfw.WindowHint(glfw.ContextVersionMajor, 4) // OR 2
|
||||
glfw.WindowHint(glfw.ContextVersionMinor, 1)
|
||||
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
|
||||
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
|
||||
|
||||
window, err := glfw.CreateWindow(width, height, "Conway's Game of Life", nil, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
window.MakeContextCurrent()
|
||||
|
||||
return window
|
||||
}
|
||||
```
|
||||
|
||||
好了,让我们花一分钟来运行一下这个程序,看看会发生什么。首先定义了一些常量, `width` 和 `height` —— 它们决定窗口的像素大小。
|
||||
|
||||
然后就是 `main` 函数。这里我们使用了 `runtime` 包的 `LockOSThread()`,这能确保我们总是在操作系统的同一个线程中运行代码,这对 GLFW 来说很重要,GLFW 需要在其被初始化之后的线程里被调用。讲完这个,接下来我们调用 `initGlfw` 来获得一个窗口的引用,并且推迟(`defer`)其终止。窗口的引用会被用在一个 `for` 循环中,只要窗口处于打开的状态,就执行某些事情。我们待会儿会讲要做的事情是什么。
|
||||
|
||||
`initGlfw` 是另一个函数,这里我们调用 `glfw.Init()` 来初始化 GLFW 包。然后我们定义了 GLFW 的一些全局属性,包括禁用调整窗口大小和改变 OpenGL 的属性。然后创建了 `glfw.Window`,这会在稍后的绘图中用到。我们仅仅告诉它我们想要的宽度和高度,以及标题,然后调用 `window.MakeContextCurrent`,将窗口绑定到当前的线程中。最后就是返回窗口的引用了。
|
||||
|
||||
如果你现在就构建、运行这个程序,你看不到任何东西。很合理,因为我们还没有用这个窗口做什么实质性的事。
|
||||
|
||||
定义一个新函数,初始化 OpenGL,就可以解决这个问题:
|
||||
|
||||
```
|
||||
// initOpenGL 初始化 OpenGL 并且返回一个初始化了的程序。
|
||||
func initOpenGL() uint32 {
|
||||
if err := gl.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
version := gl.GoStr(gl.GetString(gl.VERSION))
|
||||
log.Println("OpenGL version", version)
|
||||
|
||||
prog := gl.CreateProgram()
|
||||
gl.LinkProgram(prog)
|
||||
return prog
|
||||
}
|
||||
```
|
||||
|
||||
`initOpenGL` 就像之前的 `initGlfw` 函数一样,初始化 OpenGL 库,创建一个<ruby>程序<rt>program</rt></ruby>。“程序”是一个包含了<ruby>着色器<rt>shader</rt></ruby>的引用,稍后会用<ruby>着色器<rt>shader</rt></ruby>绘图。待会儿会讲这一点,现在只用知道 OpenGL 已经初始化完成了,我们有一个程序的引用。我们还打印了 OpenGL 的版本,可以用于之后的调试。
|
||||
|
||||
回到 `main` 函数里,调用这个新函数:
|
||||
|
||||
```
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
window := initGlfw()
|
||||
defer glfw.Terminate()
|
||||
|
||||
program := initOpenGL()
|
||||
|
||||
for !window.ShouldClose() {
|
||||
draw(window, program)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你应该注意到了现在我们有 `program` 的引用,在我们的窗口循环中,调用新的 `draw` 函数。最终这个函数会绘制出所有细胞,让游戏状态变得可视化,但是现在它做的仅仅是清除窗口,所以我们只能看到一个全黑的屏幕:
|
||||
|
||||
```
|
||||
func draw(window *glfw.Window, program uint32) {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
gl.UseProgram(prog)
|
||||
|
||||
glfw.PollEvents()
|
||||
window.SwapBuffers()
|
||||
}
|
||||
```
|
||||
|
||||
我们首先做的是调用 `gl.clear` 函数来清除上一帧在窗口中绘制的东西,给我们一个干净的面板。然后我们告诉 OpenGL 去使用我们的程序引用,这个引用还没有做什么事。最终我们告诉 GLFW 用 `PollEvents` 去检查是否有鼠标或者键盘事件(这一节里还不会对这些事件进行处理),告诉窗口去交换缓冲区 `SwapBuffers`。 [交换缓冲区][28] 很重要,因为 GLFW(像其他图形库一样)使用双缓冲,也就是说你绘制的所有东西实际上是绘制到一个不可见的画布上,当你准备好进行展示的时候就把绘制的这些东西放到可见的画布中 —— 这种情况下,就需要调用 `SwapBuffers` 函数。
|
||||
|
||||
好了,到这里我们已经讲了很多东西,花一点时间看看我们的实验成果。运行这个程序,你应该可以看到你所绘制的第一个东西:
|
||||
|
||||

|
||||
|
||||
完美!
|
||||
|
||||
### 在窗口里绘制三角形
|
||||
|
||||
我们已经完成了一些复杂的步骤,即使看起来不多,但我们仍然需要绘制一些东西。我们会以三角形绘制开始,可能这第一眼看上去要比我们最终要绘制的方形更难,但你会知道这样的想法是错的。你可能不知道的是三角形或许是绘制的图形中最简单的,实际上我们最终会用某种方式把三角形拼成方形。
|
||||
|
||||
好吧,那么我们想要绘制一个三角形,怎么做呢?我们通过定义图形的顶点来绘制图形,把它们交给 OpenGL 来进行绘制。先在 `main.go` 的顶部里定义我们的三角形:
|
||||
|
||||
```
|
||||
var (
|
||||
triangle = []float32{
|
||||
0, 0.5, 0, // top
|
||||
-0.5, -0.5, 0, // left
|
||||
0.5, -0.5, 0, // right
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
这看上去很奇怪,让我们分开来看。首先我们用了一个 `float32` <ruby>切片<rt>slice</rt></ruby>,这是一种我们总会在向 OpenGL 传递顶点时用到的数据类型。这个切片包含 9 个值,每三个值构成三角形的一个点。第一行, `0, 0.5, 0` 表示的是 X、Y、Z 坐标,是最上方的顶点,第二行是左边的顶点,第三行是右边的顶点。每一组的三个点都表示相对于窗口中心点的 X、Y、Z 坐标,大小在 `-1` 和 `1` 之间。因此最上面的顶点 X 坐标是 `0`,因为它在 X 方向上位于窗口中央,Y 坐标是 `0.5` 意味着它会相对窗口中央上移 1/4 个单位(因为窗口的范围是 `-1` 到 `1`),Z 坐标是 0。因为我们只需要在二维空间中绘图,所以 Z 值永远是 `0`。现在看一看左右两边的顶点,看看你能不能理解为什么它们是这样定义的 —— 如果不能立刻就弄清楚也没关系,我们将会在屏幕上去观察它,因此我们需要一个完美的图形来进行观察。
|
||||
|
||||
好了,我们定义了一个三角形,但是现在我们得把它画出来。要画出这个三角形,我们需要一个叫做<ruby>顶点数组对象<rt>Vertex Array Object</rt></ruby>或者叫 vao 的东西,这是由一系列的点(也就是我们定义的三角形)创造的,这个东西可以提供给 OpenGL 来进行绘制。创建一个叫做 `makeVao` 的函数,然后我们可以提供一个点的切片,让它返回一个指向 OpenGL 顶点数组对象的指针:
|
||||
|
||||
```
|
||||
// makeVao 执行初始化并从提供的点里面返回一个顶点数组
|
||||
func makeVao(points []float32) uint32 {
|
||||
var vbo uint32
|
||||
gl.GenBuffers(1, &vbo)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
||||
gl.BufferData(gl.ARRAY_BUFFER, 4*len(points), gl.Ptr(points), gl.STATIC_DRAW)
|
||||
|
||||
var vao uint32
|
||||
gl.GenVertexArrays(1, &vao)
|
||||
gl.BindVertexArray(vao)
|
||||
gl.EnableVertexAttribArray(0)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
||||
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil)
|
||||
|
||||
return vao
|
||||
}
|
||||
```
|
||||
|
||||
首先我们创造了<ruby>顶点缓冲区对象<rt>Vertex Buffer Object</rt></ruby> 或者说 vbo 绑定到我们的 `vao` 上,`vbo` 是通过所占空间(也就是 4 倍 `len(points)` 大小的空间)和一个指向顶点的指针(`gl.Ptr(points)`)来创建的。你也许会好奇为什么它是 4 倍 —— 而不是 6 或者 3 或者 1078 呢?原因在于我们用的是 `float32` 切片,32 个位的浮点型变量是 4 个字节,因此我们说这个缓冲区以字节为单位的大小是点个数的 4 倍。
|
||||
|
||||
现在我们有缓冲区了,可以创建 `vao` 并用 `gl.BindBuffer` 把它绑定到缓冲区上,最后返回 `vao`。这个 `vao` 将会被用于绘制三角形!
|
||||
|
||||
回到 `main` 函数:
|
||||
|
||||
```
|
||||
func main() {
|
||||
...
|
||||
|
||||
vao := makeVao(triangle)
|
||||
for !window.ShouldClose() {
|
||||
draw(vao, window, program)
|
||||
}
|
||||
}
|
||||
|
||||
这里我们调用了 `makeVao` ,从我们之前定义的 `triangle` 顶点中获得 `vao` 引用,将它作为一个新的参数传递给 `draw` 函数:
|
||||
|
||||
func draw(vao uint32, window *glfw.Window, program uint32) {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
gl.UseProgram(program)
|
||||
|
||||
gl.BindVertexArray(vao)
|
||||
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(triangle) / 3))
|
||||
|
||||
glfw.PollEvents()
|
||||
window.SwapBuffers()
|
||||
}
|
||||
```
|
||||
|
||||
然后我们把 OpenGL 绑定到 `vao` 上,这样当我们告诉 OpenGL 三角形切片的顶点数(除以 3,是因为每一个点有 X、Y、Z 坐标),让它去 `DrawArrays` ,它就知道要画多少个顶点了。
|
||||
|
||||
如果你这时候运行程序,你可能希望在窗口中央看到一个美丽的三角形,但是不幸的是你还看不到。还有一件事情没做,我们告诉 OpenGL 我们要画一个三角形,但是我们还要告诉它_怎么_画出来。
|
||||
|
||||
要让它画出来,我们需要叫做<ruby>片元着色器<rt>fragment shader</rt></ruby>和<ruby>顶点着色器<rt>vertex shader</rt></ruby>的东西,这些已经超出本教程的范围了(老实说,也超出了我对 OpenGL 的了解),但 [Harold Serrano 在 Quora][29] 上对对它们是什么给出了完美的介绍。我们只需要理解,对于这个应用来说,着色器是它内部的小程序(用 [OpenGL Shader Language 或 GLSL][30] 编写的),它操作顶点进行绘制,也可用于确定图形的颜色。
|
||||
|
||||
添加两个 `import` 和一个叫做 `compileShader` 的函数:
|
||||
|
||||
```
|
||||
import (
|
||||
"strings"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func compileShader(source string, shaderType uint32) (uint32, error) {
|
||||
shader := gl.CreateShader(shaderType)
|
||||
|
||||
csources, free := gl.Strs(source)
|
||||
gl.ShaderSource(shader, 1, csources, nil)
|
||||
free()
|
||||
gl.CompileShader(shader)
|
||||
|
||||
var status int32
|
||||
gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
|
||||
if status == gl.FALSE {
|
||||
var logLength int32
|
||||
gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)
|
||||
|
||||
log := strings.Repeat("\x00", int(logLength+1))
|
||||
gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))
|
||||
|
||||
return 0, fmt.Errorf("failed to compile %v: %v", source, log)
|
||||
}
|
||||
|
||||
return shader, nil
|
||||
}
|
||||
```
|
||||
|
||||
这个函数的目的是以字符串的形式接受着色器源代码和它的类型,然后返回一个指向这个编译好的着色器的指针。如果编译失败,我们就会获得出错的详细信息。
|
||||
|
||||
现在定义着色器,在 `makeProgram` 里编译。回到我们的 `const` 块中,我们在这里定义了 `width` 和 `hegiht`。
|
||||
|
||||
```
|
||||
vertexShaderSource = `
|
||||
#version 410
|
||||
in vec3 vp;
|
||||
void main() {
|
||||
gl_Position = vec4(vp, 1.0);
|
||||
}
|
||||
` + "\x00"
|
||||
|
||||
fragmentShaderSource = `
|
||||
#version 410
|
||||
out vec4 frag_colour;
|
||||
void main() {
|
||||
frag_colour = vec4(1, 1, 1, 1);
|
||||
}
|
||||
` + "\x00"
|
||||
```
|
||||
|
||||
如你所见,这是两个包含了 GLSL 源代码字符串的着色器,一个是<ruby>顶点着色器<rt>vertex shader</rt></ruby>,另一个是<ruby>片元着色器<rt>fragment shader</rt></ruby>。唯一比较特殊的地方是它们都要在末尾加上一个空终止字符,`\x00` —— OpenGL 需要它才能编译着色器。注意 `fragmentShaderSource`,这是我们用 RGBA 形式的值通过 `vec4` 来定义我们图形的颜色。你可以修改这里的值来改变这个三角形的颜色,现在的值是 `RGBA(1, 1, 1, 1)` 或者说是白色。
|
||||
|
||||
同样需要注意的是这两个程序都是运行在 `#version 410` 版本下,如果你用的是 OpenGL 2.1,那你也可以改成 `#version 120`。这里 `120` 不是打错的,如果你用的是 OpenGL 2.1,要用 `120` 而不是 `210`!
|
||||
|
||||
接下来在 `initOpenGL` 中我们会编译着色器,把它们附加到我们的 `program` 中。
|
||||
|
||||
```
|
||||
func initOpenGL() uint32 {
|
||||
if err := gl.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
version := gl.GoStr(gl.GetString(gl.VERSION))
|
||||
log.Println("OpenGL version", version)
|
||||
|
||||
vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fragmentShader, err := compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
prog := gl.CreateProgram()
|
||||
gl.AttachShader(prog, vertexShader)
|
||||
gl.AttachShader(prog, fragmentShader)
|
||||
gl.LinkProgram(prog)
|
||||
return prog
|
||||
}
|
||||
```
|
||||
|
||||
这里我们用顶点着色器(`vertexShader`)调用了 `compileShader` 函数,指定它的类型是 `gl.VERTEX_SHADER`,对片元着色器(`fragmentShader`)做了同样的事情,但是指定的类型是 `gl.FRAGMENT_SHADER`。编译完成后,我们把它们附加到程序中,调用 `gl.AttachShader`,传递程序(`prog`)以及编译好的着色器作为参数。
|
||||
|
||||
现在我们终于可以看到我们漂亮的三角形了!运行程序,如果一切顺利的话你会看到这些:
|
||||
|
||||

|
||||
|
||||
### 总结
|
||||
|
||||
是不是很惊喜!这些代码画出了一个三角形,但我保证我们已经完成了大部分的 OpenGL 代码,在接下来的章节中我们还会用到这些代码。我十分推荐你花几分钟修改一下代码,看看你能不能移动三角形,改变三角形的大小和颜色。OpenGL 可以令人心生畏惧,有时想要理解发生了什么很困难,但是要记住,这不是魔法 - 它只不过看上去像魔法。
|
||||
|
||||
下一节里我们讲会用两个锐角三角形拼出一个方形 - 看看你能不能在进入下一节前试着修改这一节的代码。不能也没有关系,因为我们在 [第二节][31] 还会编写代码, 接着创建一个有许多方形的格子,我们把它当做游戏面板。
|
||||
|
||||
最后,在[第三节][32] 里我们会用格子来实现 _Conway’s Game of Life_!
|
||||
|
||||
|
||||
### 回顾
|
||||
|
||||
本教程 `main.go` 文件的内容如下:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gl/gl/v4.1-core/gl" // OR: github.com/go-gl/gl/v2.1/gl
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
)
|
||||
|
||||
const (
|
||||
width = 500
|
||||
height = 500
|
||||
|
||||
vertexShaderSource = `
|
||||
#version 410
|
||||
in vec3 vp;
|
||||
void main() {
|
||||
gl_Position = vec4(vp, 1.0);
|
||||
}
|
||||
` + "\x00"
|
||||
|
||||
fragmentShaderSource = `
|
||||
#version 410
|
||||
out vec4 frag_colour;
|
||||
void main() {
|
||||
frag_colour = vec4(1, 1, 1, 1.0);
|
||||
}
|
||||
` + "\x00"
|
||||
)
|
||||
|
||||
var (
|
||||
triangle = []float32{
|
||||
0, 0.5, 0,
|
||||
-0.5, -0.5, 0,
|
||||
0.5, -0.5, 0,
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
window := initGlfw()
|
||||
defer glfw.Terminate()
|
||||
program := initOpenGL()
|
||||
|
||||
vao := makeVao(triangle)
|
||||
for !window.ShouldClose() {
|
||||
draw(vao, window, program)
|
||||
}
|
||||
}
|
||||
|
||||
func draw(vao uint32, window *glfw.Window, program uint32) {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
gl.UseProgram(program)
|
||||
|
||||
gl.BindVertexArray(vao)
|
||||
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(triangle)/3))
|
||||
|
||||
glfw.PollEvents()
|
||||
window.SwapBuffers()
|
||||
}
|
||||
|
||||
// initGlfw 初始化 glfw 并返回一个窗口供使用。
|
||||
func initGlfw() *glfw.Window {
|
||||
if err := glfw.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
glfw.WindowHint(glfw.Resizable, glfw.False)
|
||||
glfw.WindowHint(glfw.ContextVersionMajor, 4)
|
||||
glfw.WindowHint(glfw.ContextVersionMinor, 1)
|
||||
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
|
||||
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
|
||||
|
||||
window, err := glfw.CreateWindow(width, height, "Conway's Game of Life", nil, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
window.MakeContextCurrent()
|
||||
|
||||
return window
|
||||
}
|
||||
|
||||
// initOpenGL 初始化 OpenGL 并返回一个已经编译好的着色器程序
|
||||
func initOpenGL() uint32 {
|
||||
if err := gl.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
version := gl.GoStr(gl.GetString(gl.VERSION))
|
||||
log.Println("OpenGL version", version)
|
||||
|
||||
vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fragmentShader, err := compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
prog := gl.CreateProgram()
|
||||
gl.AttachShader(prog, vertexShader)
|
||||
gl.AttachShader(prog, fragmentShader)
|
||||
gl.LinkProgram(prog)
|
||||
return prog
|
||||
}
|
||||
|
||||
// makeVao 执行初始化并从提供的点里面返回一个顶点数组
|
||||
func makeVao(points []float32) uint32 {
|
||||
var vbo uint32
|
||||
gl.GenBuffers(1, &vbo)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
||||
gl.BufferData(gl.ARRAY_BUFFER, 4*len(points), gl.Ptr(points), gl.STATIC_DRAW)
|
||||
|
||||
var vao uint32
|
||||
gl.GenVertexArrays(1, &vao)
|
||||
gl.BindVertexArray(vao)
|
||||
gl.EnableVertexAttribArray(0)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
||||
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil)
|
||||
|
||||
return vao
|
||||
}
|
||||
|
||||
func compileShader(source string, shaderType uint32) (uint32, error) {
|
||||
shader := gl.CreateShader(shaderType)
|
||||
|
||||
csources, free := gl.Strs(source)
|
||||
gl.ShaderSource(shader, 1, csources, nil)
|
||||
free()
|
||||
gl.CompileShader(shader)
|
||||
|
||||
var status int32
|
||||
gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
|
||||
if status == gl.FALSE {
|
||||
var logLength int32
|
||||
gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)
|
||||
|
||||
log := strings.Repeat("\x00", int(logLength+1))
|
||||
gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))
|
||||
|
||||
return 0, fmt.Errorf("failed to compile %v: %v", source, log)
|
||||
}
|
||||
|
||||
return shader, nil
|
||||
}
|
||||
```
|
||||
|
||||
请在 Twitter [@kylewbanks][33] 上告诉我这篇文章对你是否有帮助,或者点击下方的关注,以便及时获取最新文章!
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl
|
||||
|
||||
作者:[kylewbanks][a]
|
||||
译者:[GitFuture](https://github.com/GitFuture)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://twitter.com/kylewbanks
|
||||
[1]:https://kylewbanks.com/category/golang
|
||||
[2]:https://kylewbanks.com/category/opengl
|
||||
[3]:https://twitter.com/intent/tweet?text=OpenGL%20%26%20Go%20Tutorial%20Part%201%3A%20Hello%2C%20OpenGL%20https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-1-hello-opengl%20by%20%40kylewbanks
|
||||
[4]:mailto:?subject=Check%20Out%20%22OpenGL%20%26%20Go%20Tutorial%20Part%201%3A%20Hello%2C%20OpenGL%22&body=https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-1-hello-opengl
|
||||
[5]:https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-1-hello-opengl
|
||||
[6]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl
|
||||
[7]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-2-drawing-the-game-board
|
||||
[8]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
[9]:https://github.com/KyleBanks/conways-gol
|
||||
[10]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl
|
||||
[11]:http://www.glfw.org/
|
||||
[12]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-2-drawing-the-game-board
|
||||
[13]:https://kylewbanks.com/blog/blog/tutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
[14]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl
|
||||
[15]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-2-drawing-the-game-board
|
||||
[16]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
[17]:https://github.com/KyleBanks/conways-gol
|
||||
[18]:https://twitter.com/kylewbanks
|
||||
[19]:https://www.opengl.org/
|
||||
[20]:https://golang.org/
|
||||
[21]:https://github.com/go-gl/gl
|
||||
[22]:https://en.wikipedia.org/wiki/Conway's_Game_of_Life
|
||||
[23]:https://github.com/KyleBanks/conways-gol
|
||||
[24]:https://en.wikipedia.org/wiki/Conway's_Game_of_Life
|
||||
[25]:https://github.com/KyleBanks/conways-gol
|
||||
[26]:http://www.glfw.org/
|
||||
[27]:https://github.com/go-gl/glfw
|
||||
[28]:http://www.glfw.org/docs/latest/window_guide.html#buffer_swap
|
||||
[29]:https://www.quora.com/What-is-a-vertex-shader-and-what-is-a-fragment-shader/answer/Harold-Serrano?srid=aVb
|
||||
[30]:https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/glsl_overview.php
|
||||
[31]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-2-drawing-the-game-board
|
||||
[32]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
[33]:https://twitter.com/kylewbanks
|
||||
@@ -0,0 +1,509 @@
|
||||
OpenGL 与 Go 教程(二)绘制游戏面板
|
||||
============================================================
|
||||
|
||||
- [第一节: Hello, OpenGL][6]
|
||||
- [第二节: 绘制游戏面板][7]
|
||||
- [第三节:实现游戏功能][8]
|
||||
|
||||
这篇教程的所有源代码都可以在 [GitHub][9] 上找到。
|
||||
|
||||
欢迎回到《OpenGL 与 Go 教程》。如果你还没有看过[第一节][15],那就要回过头去看看那一节。
|
||||
|
||||
你现在应该能够创造一个漂亮的白色三角形,但我们不会把三角形当成我们游戏的基本单元,是时候把三角形变成正方形了,然后我们会做出一个完整的方格。
|
||||
|
||||
让我们现在开始做吧!
|
||||
|
||||
### 利用三角形绘制方形
|
||||
|
||||
在我们绘制方形之前,先把三角形变成直角三角形。打开 `main.go` 文件,把 `triangle` 的定义改成像这个样子:
|
||||
|
||||
```
|
||||
triangle = []float32{
|
||||
-0.5, 0.5, 0,
|
||||
-0.5, -0.5, 0,
|
||||
0.5, -0.5, 0,
|
||||
}
|
||||
```
|
||||
|
||||
我们做的事情是,把最上面的顶点 X 坐标移动到左边(也就是变为 `-0.5`),这就变成了像这样的三角形:
|
||||
|
||||

|
||||
|
||||
很简单,对吧?现在让我们用两个这样的三角形顶点做成正方形。把 `triangle` 重命名为 `square`,然后添加第二个倒置的三角形的顶点数据,把直角三角形变成这样的:
|
||||
|
||||
```
|
||||
square = []float32{
|
||||
-0.5, 0.5, 0,
|
||||
-0.5, -0.5, 0,
|
||||
0.5, -0.5, 0,
|
||||
|
||||
-0.5, 0.5, 0,
|
||||
0.5, 0.5, 0,
|
||||
0.5, -0.5, 0,
|
||||
}
|
||||
```
|
||||
|
||||
注意:你也要把在 `main` 和 `draw` 里面命名的 `triangle` 改为 `square`。
|
||||
|
||||
我们通过添加三个顶点,把顶点数增加了一倍,这三个顶点就是右上角的三角形,用来拼成方形。运行它看看效果:
|
||||
|
||||

|
||||
|
||||
很好,现在我们能够绘制正方形了!OpenGL 一点都不难,对吧?
|
||||
|
||||
### 在窗口中绘制方形格子
|
||||
|
||||
现在我们能画一个方形,怎么画 100 个吗?我们来创建一个 `cell` 结构体,用来表示格子的每一个单元,因此我们能够很灵活的选择绘制的数量:
|
||||
|
||||
```
|
||||
type cell struct {
|
||||
drawable uint32
|
||||
|
||||
x int
|
||||
y int
|
||||
}
|
||||
```
|
||||
|
||||
`cell` 结构体包含一个 `drawable` 属性,这是一个顶点数组对象,就像我们在之前创建的一样,这个结构体还包含 X 和 Y 坐标,用来表示这个格子的位置。
|
||||
|
||||
我们还需要两个常量,用来设定格子的大小和形状:
|
||||
|
||||
```
|
||||
const (
|
||||
...
|
||||
|
||||
rows = 10
|
||||
columns = 10
|
||||
)
|
||||
```
|
||||
|
||||
现在我们添加一个创建格子的函数:
|
||||
|
||||
```
|
||||
func makeCells() [][]*cell {
|
||||
cells := make([][]*cell, rows, rows)
|
||||
for x := 0; x < rows; x++ {
|
||||
for y := 0; y < columns; y++ {
|
||||
c := newCell(x, y)
|
||||
cells[x] = append(cells[x], c)
|
||||
}
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
```
|
||||
|
||||
这里我们创建多维的<ruby>切片<rt>slice</rt></ruby>,代表我们的游戏面板,用名为 `newCell` 的新函数创建的 `cell` 来填充矩阵的每个元素,我们待会就来实现 `newCell` 这个函数。
|
||||
|
||||
在接着往下阅读前,我们先花一点时间来看看 `makeCells` 函数做了些什么。我们创造了一个切片,这个切片的长度和格子的行数相等,每一个切片里面都有一个<ruby>细胞<rt>cell</rt></ruby>的切片,这些细胞的数量与列数相等。如果我们把 `rows` 和 `columns` 都设定成 2,那么就会创建如下的矩阵:
|
||||
|
||||
```
|
||||
[
|
||||
[cell, cell],
|
||||
[cell, cell]
|
||||
]
|
||||
```
|
||||
|
||||
还可以创建一个更大的矩阵,包含 `10x10` 个细胞:
|
||||
|
||||
```
|
||||
[
|
||||
[cell, cell, cell, cell, cell, cell, cell, cell, cell, cell],
|
||||
[cell, cell, cell, cell, cell, cell, cell, cell, cell, cell],
|
||||
[cell, cell, cell, cell, cell, cell, cell, cell, cell, cell],
|
||||
[cell, cell, cell, cell, cell, cell, cell, cell, cell, cell],
|
||||
[cell, cell, cell, cell, cell, cell, cell, cell, cell, cell],
|
||||
[cell, cell, cell, cell, cell, cell, cell, cell, cell, cell],
|
||||
[cell, cell, cell, cell, cell, cell, cell, cell, cell, cell],
|
||||
[cell, cell, cell, cell, cell, cell, cell, cell, cell, cell],
|
||||
[cell, cell, cell, cell, cell, cell, cell, cell, cell, cell],
|
||||
[cell, cell, cell, cell, cell, cell, cell, cell, cell, cell]
|
||||
]
|
||||
```
|
||||
|
||||
现在应该理解了我们创造的矩阵的形状和表示方法。让我们看看 `newCell` 函数到底是怎么填充矩阵的:
|
||||
|
||||
```
|
||||
func newCell(x, y int) *cell {
|
||||
points := make([]float32, len(square), len(square))
|
||||
copy(points, square)
|
||||
|
||||
for i := 0; i < len(points); i++ {
|
||||
var position float32
|
||||
var size float32
|
||||
switch i % 3 {
|
||||
case 0:
|
||||
size = 1.0 / float32(columns)
|
||||
position = float32(x) * size
|
||||
case 1:
|
||||
size = 1.0 / float32(rows)
|
||||
position = float32(y) * size
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if points[i] < 0 {
|
||||
points[i] = (position * 2) - 1
|
||||
} else {
|
||||
points[i] = ((position + size) * 2) - 1
|
||||
}
|
||||
}
|
||||
|
||||
return &cell{
|
||||
drawable: makeVao(points),
|
||||
|
||||
x: x,
|
||||
y: y,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个函数里有很多内容,我们把它分成几个部分。我们做的第一件事是复制了 `square` 的定义。这让我们能够修改该定义,定制当前的细胞位置,而不会影响其它使用 `square` 切片定义的细胞。然后我们基于当前索引迭代 `points` 副本。我们用求余数的方法来判断我们是在操作 X 坐标(`i % 3 == 0`),还是在操作 Y 坐标(`i % 3 == 1`)(跳过 Z 坐标是因为我们仅在二维层面上进行操作),跟着确定细胞的大小(也就是占据整个游戏面板的比例),当然它的位置是基于细胞在 `相对游戏面板的` X 和 Y 坐标。
|
||||
|
||||
接着,我们改变那些包含在 `square` 切片中定义的 `0.5`,`0`, `-0.5` 这样的点。如果点小于 0,我们就把它设置成原来的 2 倍(因为 OpenGL 坐标的范围在 `-1` 到 `1` 之间,范围大小是 2),减 1 是为了归一化 OpenGL 坐标。如果点大于等于 0,我们的做法还是一样的,不过要加上我们计算出的尺寸。
|
||||
|
||||
这样做是为了设置每个细胞的大小,这样它就能只填充它在面板中的部分。因为我们有 10 行 10 列,每一个格子能分到游戏面板的 10% 宽度和高度。
|
||||
|
||||
最后,确定了所有点的位置和大小,我们用提供的 X 和 Y 坐标创建一个 `cell`,并设置 `drawable` 字段与我们刚刚操作 `points` 得到的顶点数组对象(vao)一致。
|
||||
|
||||
好了,现在我们在 `main` 函数里可以移去对 `makeVao` 的调用了,用 `makeCells` 代替。我们还修改了 `draw`,让它绘制一系列的细胞而不是一个 `vao`。
|
||||
|
||||
```
|
||||
func main() {
|
||||
...
|
||||
|
||||
// vao := makeVao(square)
|
||||
cells := makeCells()
|
||||
|
||||
for !window.ShouldClose() {
|
||||
draw(cells, window, program)
|
||||
}
|
||||
}
|
||||
|
||||
func draw(cells [][]*cell, window *glfw.Window, program uint32) {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
gl.UseProgram(program)
|
||||
|
||||
// TODO
|
||||
|
||||
glfw.PollEvents()
|
||||
window.SwapBuffers()
|
||||
}
|
||||
```
|
||||
|
||||
现在我们要让每个细胞知道怎么绘制出自己。在 `cell` 里面添加一个 `draw` 函数:
|
||||
|
||||
```
|
||||
func (c *cell) draw() {
|
||||
gl.BindVertexArray(c.drawable)
|
||||
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(square) / 3))
|
||||
}
|
||||
```
|
||||
|
||||
这看上去很熟悉,它很像我们之前在 `vao` 里写的 `draw`,唯一的区别是我们的 `BindVertexArray` 函数用的是 `c.drawable`,这是我们在 `newCell` 中创造的细胞的 `vao`。
|
||||
|
||||
回到 main 中的 `draw` 函数上,我们可以循环每个细胞,让它们自己绘制自己:
|
||||
|
||||
```
|
||||
func draw(cells [][]*cell, window *glfw.Window, program uint32) {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
gl.UseProgram(program)
|
||||
|
||||
for x := range cells {
|
||||
for _, c := range cells[x] {
|
||||
c.draw()
|
||||
}
|
||||
}
|
||||
|
||||
glfw.PollEvents()
|
||||
window.SwapBuffers()
|
||||
}
|
||||
```
|
||||
|
||||
如你所见,我们循环每一个细胞,调用它的 `draw` 函数。如果运行这段代码,你能看到像下面这样的东西:
|
||||
|
||||

|
||||
|
||||
这是你想看到的吗?我们做的是在格子里为每一行每一列创建了一个方块,然后给它上色,这就填满了整个面板!
|
||||
|
||||
注释掉 for 循环,我们就可以看到一个明显独立的细胞,像这样:
|
||||
|
||||
```
|
||||
// for x := range cells {
|
||||
// for _, c := range cells[x] {
|
||||
// c.draw()
|
||||
// }
|
||||
// }
|
||||
|
||||
cells[2][3].draw()
|
||||
```
|
||||
|
||||

|
||||
|
||||
这只绘制坐标在 `(X=2, Y=3)` 的格子。你可以看到,每一个独立的细胞占据着面板的一小块部分,并且负责绘制自己那部分空间。我们也能看到游戏面板有自己的原点,也就是坐标为 `(X=0, Y=0)` 的点,在窗口的左下方。这仅仅是我们的 `newCell` 函数计算位置的方式,也可以用右上角,右下角,左上角,中央,或者其它任何位置当作原点。
|
||||
|
||||
接着往下做,移除 `cells[2][3].draw()` 这一行,取消 for 循环的那部分注释,变成之前那样全部绘制的样子。
|
||||
|
||||
### 总结
|
||||
|
||||
好了,我们现在能用两个三角形画出一个正方形了,我们还有一个游戏的面板了!我们该为此自豪,目前为止我们已经接触到了很多零碎的内容,老实说,最难的部分还在前面等着我们!
|
||||
|
||||
在接下来的第三节,我们会实现游戏核心逻辑,看到很酷的东西!
|
||||
|
||||
### 回顾
|
||||
|
||||
这是这一部分教程中 `main.go` 文件的内容:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gl/gl/v4.1-core/gl" // OR: github.com/go-gl/gl/v2.1/gl
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
)
|
||||
|
||||
const (
|
||||
width = 500
|
||||
height = 500
|
||||
|
||||
vertexShaderSource = `
|
||||
#version 410
|
||||
in vec3 vp;
|
||||
void main() {
|
||||
gl_Position = vec4(vp, 1.0);
|
||||
}
|
||||
` + "\x00"
|
||||
|
||||
fragmentShaderSource = `
|
||||
#version 410
|
||||
out vec4 frag_colour;
|
||||
void main() {
|
||||
frag_colour = vec4(1, 1, 1, 1.0);
|
||||
}
|
||||
` + "\x00"
|
||||
|
||||
rows = 10
|
||||
columns = 10
|
||||
)
|
||||
|
||||
var (
|
||||
square = []float32{
|
||||
-0.5, 0.5, 0,
|
||||
-0.5, -0.5, 0,
|
||||
0.5, -0.5, 0,
|
||||
|
||||
-0.5, 0.5, 0,
|
||||
0.5, 0.5, 0,
|
||||
0.5, -0.5, 0,
|
||||
}
|
||||
)
|
||||
|
||||
type cell struct {
|
||||
drawable uint32
|
||||
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
window := initGlfw()
|
||||
defer glfw.Terminate()
|
||||
program := initOpenGL()
|
||||
|
||||
cells := makeCells()
|
||||
for !window.ShouldClose() {
|
||||
draw(cells, window, program)
|
||||
}
|
||||
}
|
||||
|
||||
func draw(cells [][]*cell, window *glfw.Window, program uint32) {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
gl.UseProgram(program)
|
||||
|
||||
for x := range cells {
|
||||
for _, c := range cells[x] {
|
||||
c.draw()
|
||||
}
|
||||
}
|
||||
|
||||
glfw.PollEvents()
|
||||
window.SwapBuffers()
|
||||
}
|
||||
|
||||
func makeCells() [][]*cell {
|
||||
cells := make([][]*cell, rows, rows)
|
||||
for x := 0; x < rows; x++ {
|
||||
for y := 0; y < columns; y++ {
|
||||
c := newCell(x, y)
|
||||
cells[x] = append(cells[x], c)
|
||||
}
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
|
||||
func newCell(x, y int) *cell {
|
||||
points := make([]float32, len(square), len(square))
|
||||
copy(points, square)
|
||||
|
||||
for i := 0; i < len(points); i++ {
|
||||
var position float32
|
||||
var size float32
|
||||
switch i % 3 {
|
||||
case 0:
|
||||
size = 1.0 / float32(columns)
|
||||
position = float32(x) * size
|
||||
case 1:
|
||||
size = 1.0 / float32(rows)
|
||||
position = float32(y) * size
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if points[i] < 0 {
|
||||
points[i] = (position * 2) - 1
|
||||
} else {
|
||||
points[i] = ((position + size) * 2) - 1
|
||||
}
|
||||
}
|
||||
|
||||
return &cell{
|
||||
drawable: makeVao(points),
|
||||
|
||||
x: x,
|
||||
y: y,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cell) draw() {
|
||||
gl.BindVertexArray(c.drawable)
|
||||
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(square)/3))
|
||||
}
|
||||
|
||||
// 初始化 glfw,返回一个可用的 Window
|
||||
func initGlfw() *glfw.Window {
|
||||
if err := glfw.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
glfw.WindowHint(glfw.Resizable, glfw.False)
|
||||
glfw.WindowHint(glfw.ContextVersionMajor, 4)
|
||||
glfw.WindowHint(glfw.ContextVersionMinor, 1)
|
||||
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
|
||||
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
|
||||
|
||||
window, err := glfw.CreateWindow(width, height, "Conway's Game of Life", nil, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
window.MakeContextCurrent()
|
||||
|
||||
return window
|
||||
}
|
||||
|
||||
// 初始化 OpenGL 并返回一个可用的着色器程序
|
||||
func initOpenGL() uint32 {
|
||||
if err := gl.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
version := gl.GoStr(gl.GetString(gl.VERSION))
|
||||
log.Println("OpenGL version", version)
|
||||
|
||||
vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fragmentShader, err := compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
prog := gl.CreateProgram()
|
||||
gl.AttachShader(prog, vertexShader)
|
||||
gl.AttachShader(prog, fragmentShader)
|
||||
gl.LinkProgram(prog)
|
||||
return prog
|
||||
}
|
||||
|
||||
// 初始化并返回由 points 提供的顶点数组
|
||||
func makeVao(points []float32) uint32 {
|
||||
var vbo uint32
|
||||
gl.GenBuffers(1, &vbo)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
||||
gl.BufferData(gl.ARRAY_BUFFER, 4*len(points), gl.Ptr(points), gl.STATIC_DRAW)
|
||||
|
||||
var vao uint32
|
||||
gl.GenVertexArrays(1, &vao)
|
||||
gl.BindVertexArray(vao)
|
||||
gl.EnableVertexAttribArray(0)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
||||
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil)
|
||||
|
||||
return vao
|
||||
}
|
||||
|
||||
func compileShader(source string, shaderType uint32) (uint32, error) {
|
||||
shader := gl.CreateShader(shaderType)
|
||||
|
||||
csources, free := gl.Strs(source)
|
||||
gl.ShaderSource(shader, 1, csources, nil)
|
||||
free()
|
||||
gl.CompileShader(shader)
|
||||
|
||||
var status int32
|
||||
gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
|
||||
if status == gl.FALSE {
|
||||
var logLength int32
|
||||
gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)
|
||||
|
||||
log := strings.Repeat("\x00", int(logLength+1))
|
||||
gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))
|
||||
|
||||
return 0, fmt.Errorf("failed to compile %v: %v", source, log)
|
||||
}
|
||||
|
||||
return shader, nil
|
||||
}
|
||||
```
|
||||
|
||||
让我知道这篇文章对你有没有帮助,在 Twitter [@kylewbanks][20] 或者下方的连接,关注我以便获取最新的文章!
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-2-drawing-the-game-board
|
||||
|
||||
作者:[kylewbanks][a]
|
||||
译者:[GitFtuture](https://github.com/GitFuture)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://twitter.com/kylewbanks
|
||||
[1]:https://kylewbanks.com/category/golang
|
||||
[2]:https://kylewbanks.com/category/opengl
|
||||
[3]:https://twitter.com/intent/tweet?text=OpenGL%20%26%20Go%20Tutorial%20Part%202%3A%20Drawing%20the%20Game%20Board%20https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-2-drawing-the-game-board%20by%20%40kylewbanks
|
||||
[4]:mailto:?subject=Check%20Out%20%22OpenGL%20%26%20Go%20Tutorial%20Part%202%3A%20Drawing%20the%20Game%20Board%22&body=https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-2-drawing-the-game-board
|
||||
[5]:https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-2-drawing-the-game-board
|
||||
[6]:https://linux.cn/article-8933-1.html
|
||||
[7]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-2-drawing-the-game-board
|
||||
[8]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
[9]:https://github.com/KyleBanks/conways-gol
|
||||
[10]:https://linux.cn/article-8933-1.html
|
||||
[11]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-2-drawing-the-game-board
|
||||
[12]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
[13]:https://github.com/KyleBanks/conways-gol
|
||||
[14]:https://twitter.com/kylewbanks
|
||||
[15]:https://linux.cn/article-8933-1.html
|
||||
[16]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
[17]:https://twitter.com/intent/tweet?text=OpenGL%20%26%20Go%20Tutorial%20Part%202%3A%20Drawing%20the%20Game%20Board%20https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-2-drawing-the-game-board%20by%20%40kylewbanks
|
||||
[18]:mailto:?subject=Check%20Out%20%22OpenGL%20%26%20Go%20Tutorial%20Part%202%3A%20Drawing%20the%20Game%20Board%22&body=https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-2-drawing-the-game-board
|
||||
[19]:https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-2-drawing-the-game-board
|
||||
[20]:https://twitter.com/kylewbanks
|
||||
@@ -0,0 +1,608 @@
|
||||
OpenGL 与 Go 教程(三)实现游戏
|
||||
============================================================
|
||||
|
||||
- [第一节: Hello, OpenGL][8]
|
||||
- [第二节: 绘制游戏面板][9]
|
||||
- [第三节:实现游戏功能][10]
|
||||
|
||||
该教程的完整源代码可以从 [GitHub][11] 上找到。
|
||||
|
||||
欢迎回到《OpenGL 与 Go 教程》!如果你还没有看过 [第一节][12] 和 [第二节][13],那就要回过头去看一看。
|
||||
|
||||
到目前为止,你应该懂得如何创建网格系统以及创建代表方格中每一个单元的格子阵列。现在可以开始把网格当作游戏面板实现<ruby>康威生命游戏<rt>Conway's Game of Life</rt></ruby>。
|
||||
|
||||
开始吧!
|
||||
|
||||
### 实现康威生命游戏
|
||||
|
||||
康威生命游戏的其中一个要点是所有<ruby>细胞<rt>cell</rt></ruby>必须同时基于当前细胞在面板中的状态确定下一个细胞的状态。也就是说如果细胞 `(X=3,Y=4)` 在计算过程中状态发生了改变,那么邻近的细胞 `(X=4,Y=4)` 必须基于 `(X=3,Y=4)` 的状态决定自己的状态变化,而不是基于自己现在的状态。简单的讲,这意味着我们必须遍历细胞,确定下一个细胞的状态,而在绘制之前不改变他们的当前状态,然后在下一次循环中我们将新状态应用到游戏里,依此循环往复。
|
||||
|
||||
为了完成这个功能,我们需要在 `cell` 结构体中添加两个布尔型变量:
|
||||
|
||||
```
|
||||
type cell struct {
|
||||
drawable uint32
|
||||
|
||||
alive bool
|
||||
aliveNext bool
|
||||
|
||||
x int
|
||||
y int
|
||||
}
|
||||
```
|
||||
|
||||
这里我们添加了 `alive` 和 `aliveNext`,前一个是细胞当前的专题,后一个是经过计算后下一回合的状态。
|
||||
|
||||
现在添加两个函数,我们会用它们来确定 cell 的状态:
|
||||
|
||||
```
|
||||
// checkState 函数决定下一次游戏循环时的 cell 状态
|
||||
func (c *cell) checkState(cells [][]*cell) {
|
||||
c.alive = c.aliveNext
|
||||
c.aliveNext = c.alive
|
||||
|
||||
liveCount := c.liveNeighbors(cells)
|
||||
if c.alive {
|
||||
// 1. 当任何一个存活的 cell 的附近少于 2 个存活的 cell 时,该 cell 将会消亡,就像人口过少所导致的结果一样
|
||||
if liveCount < 2 {
|
||||
c.aliveNext = false
|
||||
}
|
||||
|
||||
// 2. 当任何一个存活的 cell 的附近有 2 至 3 个存活的 cell 时,该 cell 在下一代中仍然存活。
|
||||
if liveCount == 2 || liveCount == 3 {
|
||||
c.aliveNext = true
|
||||
}
|
||||
|
||||
// 3. 当任何一个存活的 cell 的附近多于 3 个存活的 cell 时,该 cell 将会消亡,就像人口过多所导致的结果一样
|
||||
if liveCount > 3 {
|
||||
c.aliveNext = false
|
||||
}
|
||||
} else {
|
||||
// 4. 任何一个消亡的 cell 附近刚好有 3 个存活的 cell,该 cell 会变为存活的状态,就像重生一样。
|
||||
if liveCount == 3 {
|
||||
c.aliveNext = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// liveNeighbors 函数返回当前 cell 附近存活的 cell 数
|
||||
func (c *cell) liveNeighbors(cells [][]*cell) int {
|
||||
var liveCount int
|
||||
add := func(x, y int) {
|
||||
// If we're at an edge, check the other side of the board.
|
||||
if x == len(cells) {
|
||||
x = 0
|
||||
} else if x == -1 {
|
||||
x = len(cells) - 1
|
||||
}
|
||||
if y == len(cells[x]) {
|
||||
y = 0
|
||||
} else if y == -1 {
|
||||
y = len(cells[x]) - 1
|
||||
}
|
||||
|
||||
if cells[x][y].alive {
|
||||
liveCount++
|
||||
}
|
||||
}
|
||||
|
||||
add(c.x-1, c.y) // To the left
|
||||
add(c.x+1, c.y) // To the right
|
||||
add(c.x, c.y+1) // up
|
||||
add(c.x, c.y-1) // down
|
||||
add(c.x-1, c.y+1) // top-left
|
||||
add(c.x+1, c.y+1) // top-right
|
||||
add(c.x-1, c.y-1) // bottom-left
|
||||
add(c.x+1, c.y-1) // bottom-right
|
||||
|
||||
return liveCount
|
||||
}
|
||||
```
|
||||
|
||||
在 `checkState` 中我们设置当前状态(`alive`) 等于我们最近迭代结果(`aliveNext`)。接下来我们计数邻居数量,并根据游戏的规则来决定 `aliveNext` 状态。该规则是比较清晰的,而且我们在上面的代码当中也有说明,所以这里不再赘述。
|
||||
|
||||
更加值得注意的是 `liveNeighbors` 函数里,我们返回的是当前处于存活(`alive`)状态的细胞的邻居个数。我们定义了一个叫做 `add` 的内嵌函数,它会对 `X` 和 `Y` 坐标做一些重复性的验证。它所做的事情是检查我们传递的数字是否超出了范围——比如说,如果细胞 `(X=0,Y=5)` 想要验证它左边的细胞,它就得验证面板另一边的细胞 `(X=9,Y=5)`,Y 轴与之类似。
|
||||
|
||||
在 `add` 内嵌函数后面,我们给当前细胞附近的八个细胞分别调用 `add` 函数,示意如下:
|
||||
|
||||
```
|
||||
[
|
||||
[-, -, -],
|
||||
[N, N, N],
|
||||
[N, C, N],
|
||||
[N, N, N],
|
||||
[-, -, -]
|
||||
]
|
||||
```
|
||||
|
||||
在该示意中,每一个叫做 N 的细胞是 C 的邻居。
|
||||
|
||||
现在是我们的 `main` 函数,这里我们执行核心游戏循环,调用每个细胞的 `checkState` 函数进行绘制:
|
||||
|
||||
```
|
||||
func main() {
|
||||
...
|
||||
|
||||
for !window.ShouldClose() {
|
||||
for x := range cells {
|
||||
for _, c := range cells[x] {
|
||||
c.checkState(cells)
|
||||
}
|
||||
}
|
||||
|
||||
draw(cells, window, program)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在我们的游戏逻辑全都设置好了,我们需要修改细胞绘制函数来跳过绘制不存活的细胞:
|
||||
|
||||
```
|
||||
func (c *cell) draw() {
|
||||
if !c.alive {
|
||||
return
|
||||
}
|
||||
|
||||
gl.BindVertexArray(c.drawable)
|
||||
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(square)/3))
|
||||
}
|
||||
```
|
||||
|
||||
如果我们现在运行这个游戏,你将看到一个纯黑的屏幕,而不是我们辛苦工作后应该看到生命模拟。为什么呢?其实这正是模拟在工作。因为我们没有活着的细胞,所以就一个都不会绘制出来。
|
||||
|
||||
|
||||
现在完善这个函数。回到 `makeCells` 函数,我们用 `0.0` 到 `1.0` 之间的一个随机数来设置游戏的初始状态。我们会定义一个大小为 `0.15` 的常量阈值,也就是说每个细胞都有 15% 的几率处于存活状态。
|
||||
|
||||
```
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
...
|
||||
)
|
||||
|
||||
const (
|
||||
...
|
||||
|
||||
threshold = 0.15
|
||||
)
|
||||
|
||||
func makeCells() [][]*cell {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
cells := make([][]*cell, rows, rows)
|
||||
for x := 0; x < rows; x++ {
|
||||
for y := 0; y < columns; y++ {
|
||||
c := newCell(x, y)
|
||||
|
||||
c.alive = rand.Float64() < threshold
|
||||
c.aliveNext = c.alive
|
||||
|
||||
cells[x] = append(cells[x], c)
|
||||
}
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
```
|
||||
|
||||
我们首先增加两个引入:随机(`math/rand`)和时间(`time`),并定义我们的常量阈值。然后在 `makeCells` 中我们使用当前时间作为随机种子,给每个游戏一个独特的起始状态。你也可也指定一个特定的种子值,来始终得到一个相同的游戏,这在你想重放某个有趣的模拟时很有用。
|
||||
|
||||
接下来在循环中,在用 `newCell` 函数创造一个新的细胞时,我们根据随机浮点数的大小设置它的存活状态,随机数在 `0.0` 到 `1.0` 之间,如果比阈值(`0.15`)小,就是存活状态。再次强调,这意味着每个细胞在开始时都有 15% 的几率是存活的。你可以修改数值大小,增加或者减少当前游戏中存活的细胞。我们还把 `aliveNext` 设成 `alive` 状态,否则在第一次迭代之后我们会发现一大片细胞消亡了,这是因为 `aliveNext` 将永远是 `false`。
|
||||
|
||||
现在继续运行它,你很有可能看到细胞们一闪而过,但你却无法理解这是为什么。原因可能在于你的电脑太快了,在你能够看清楚之前就运行了(甚至完成了)模拟过程。
|
||||
|
||||
让我们降低游戏速度,在主循环中引入一个帧率(FPS)限制:
|
||||
|
||||
```
|
||||
const (
|
||||
...
|
||||
|
||||
fps = 2
|
||||
)
|
||||
|
||||
func main() {
|
||||
...
|
||||
|
||||
for !window.ShouldClose() {
|
||||
t := time.Now()
|
||||
|
||||
for x := range cells {
|
||||
for _, c := range cells[x] {
|
||||
c.checkState(cells)
|
||||
}
|
||||
}
|
||||
|
||||
if err := draw(prog, window, cells); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second/time.Duration(fps) - time.Since(t))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在你能给看出一些图案了,尽管它变换的很慢。把 FPS 加到 10,把方格的尺寸加到 100x100,你就能看到更真实的模拟:
|
||||
|
||||
```
|
||||
const (
|
||||
...
|
||||
|
||||
rows = 100
|
||||
columns = 100
|
||||
|
||||
fps = 10
|
||||
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
试着修改常量,看看它们是怎么影响模拟过程的 —— 这是你用 Go 语言写的第一个 OpenGL 程序,很酷吧?
|
||||
|
||||
### 进阶内容?
|
||||
|
||||
这是《OpenGL 与 Go 教程》的最后一节,但是这不意味着到此而止。这里有些新的挑战,能够增进你对 OpenGL (以及 Go)的理解。
|
||||
|
||||
1. 给每个细胞一种不同的颜色。
|
||||
2. 让用户能够通过命令行参数指定格子尺寸、帧率、种子和阈值。在 GitHub 上的 [github.com/KyleBanks/conways-gol][4] 里你可以看到一个已经实现的程序。
|
||||
3. 把格子的形状变成其它更有意思的,比如六边形。
|
||||
4. 用颜色表示细胞的状态 —— 比如,在第一帧把存活状态的格子设成绿色,如果它们存活了超过三帧的时间,就变成黄色。
|
||||
5. 如果模拟过程结束了,就自动关闭窗口,也就是说所有细胞都消亡了,或者是最后两帧里没有格子的状态有改变。
|
||||
6. 将着色器源代码放到单独的文件中,而不是把它们用字符串的形式放在 Go 的源代码中。
|
||||
|
||||
### 总结
|
||||
|
||||
希望这篇教程对想要入门 OpenGL (或者是 Go)的人有所帮助!这很有趣,因此我也希望理解学习它也很有趣。
|
||||
|
||||
正如我所说的,OpenGL 可能是非常恐怖的,但只要你开始着手了就不会太差。你只用制定一个个可达成的小目标,然后享受每一次成功,因为尽管 OpenGL 不会总像它看上去的那么难,但也肯定有些难懂的东西。我发现,当遇到一个难于理解用 go-gl 生成的代码的 OpenGL 问题时,你总是可以参考一下在网上更流行的当作教程的 C 语言代码,这很有用。通常 C 语言和 Go 语言的唯一区别是在 Go 中,gl 函数的前缀是 `gl.` 而不是 `gl`,常量的前缀是 `gl` 而不是 `GL_`。这可以极大地增加了你的绘制知识!
|
||||
|
||||
该教程的完整源代码可从 [GitHub][17] 上获得。
|
||||
|
||||
### 回顾
|
||||
|
||||
这是 main.go 文件最终的内容:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-gl/gl/v4.1-core/gl" // OR: github.com/go-gl/gl/v2.1/gl
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
)
|
||||
|
||||
const (
|
||||
width = 500
|
||||
height = 500
|
||||
|
||||
vertexShaderSource = `
|
||||
#version 410
|
||||
in vec3 vp;
|
||||
void main() {
|
||||
gl_Position = vec4(vp, 1.0);
|
||||
}
|
||||
` + "\x00"
|
||||
|
||||
fragmentShaderSource = `
|
||||
#version 410
|
||||
out vec4 frag_colour;
|
||||
void main() {
|
||||
frag_colour = vec4(1, 1, 1, 1.0);
|
||||
}
|
||||
` + "\x00"
|
||||
|
||||
rows = 100
|
||||
columns = 100
|
||||
|
||||
threshold = 0.15
|
||||
fps = 10
|
||||
)
|
||||
|
||||
var (
|
||||
square = []float32{
|
||||
-0.5, 0.5, 0,
|
||||
-0.5, -0.5, 0,
|
||||
0.5, -0.5, 0,
|
||||
|
||||
-0.5, 0.5, 0,
|
||||
0.5, 0.5, 0,
|
||||
0.5, -0.5, 0,
|
||||
}
|
||||
)
|
||||
|
||||
type cell struct {
|
||||
drawable uint32
|
||||
|
||||
alive bool
|
||||
aliveNext bool
|
||||
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
window := initGlfw()
|
||||
defer glfw.Terminate()
|
||||
program := initOpenGL()
|
||||
|
||||
cells := makeCells()
|
||||
for !window.ShouldClose() {
|
||||
t := time.Now()
|
||||
|
||||
for x := range cells {
|
||||
for _, c := range cells[x] {
|
||||
c.checkState(cells)
|
||||
}
|
||||
}
|
||||
|
||||
draw(cells, window, program)
|
||||
|
||||
time.Sleep(time.Second/time.Duration(fps) - time.Since(t))
|
||||
}
|
||||
}
|
||||
|
||||
func draw(cells [][]*cell, window *glfw.Window, program uint32) {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
gl.UseProgram(program)
|
||||
|
||||
for x := range cells {
|
||||
for _, c := range cells[x] {
|
||||
c.draw()
|
||||
}
|
||||
}
|
||||
|
||||
glfw.PollEvents()
|
||||
window.SwapBuffers()
|
||||
}
|
||||
|
||||
func makeCells() [][]*cell {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
cells := make([][]*cell, rows, rows)
|
||||
for x := 0; x < rows; x++ {
|
||||
for y := 0; y < columns; y++ {
|
||||
c := newCell(x, y)
|
||||
|
||||
c.alive = rand.Float64() < threshold
|
||||
c.aliveNext = c.alive
|
||||
|
||||
cells[x] = append(cells[x], c)
|
||||
}
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
func newCell(x, y int) *cell {
|
||||
points := make([]float32, len(square), len(square))
|
||||
copy(points, square)
|
||||
|
||||
for i := 0; i < len(points); i++ {
|
||||
var position float32
|
||||
var size float32
|
||||
switch i % 3 {
|
||||
case 0:
|
||||
size = 1.0 / float32(columns)
|
||||
position = float32(x) * size
|
||||
case 1:
|
||||
size = 1.0 / float32(rows)
|
||||
position = float32(y) * size
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if points[i] < 0 {
|
||||
points[i] = (position * 2) - 1
|
||||
} else {
|
||||
points[i] = ((position + size) * 2) - 1
|
||||
}
|
||||
}
|
||||
|
||||
return &cell{
|
||||
drawable: makeVao(points),
|
||||
|
||||
x: x,
|
||||
y: y,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cell) draw() {
|
||||
if !c.alive {
|
||||
return
|
||||
}
|
||||
|
||||
gl.BindVertexArray(c.drawable)
|
||||
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(square)/3))
|
||||
}
|
||||
|
||||
// checkState 函数决定下一次游戏循环时的 cell 状态
|
||||
func (c *cell) checkState(cells [][]*cell) {
|
||||
c.alive = c.aliveNext
|
||||
c.aliveNext = c.alive
|
||||
|
||||
liveCount := c.liveNeighbors(cells)
|
||||
if c.alive {
|
||||
// 1. 当任何一个存活的 cell 的附近少于 2 个存活的 cell 时,该 cell 将会消亡,就像人口过少所导致的结果一样
|
||||
if liveCount < 2 {
|
||||
c.aliveNext = false
|
||||
}
|
||||
|
||||
// 2. 当任何一个存活的 cell 的附近有 2 至 3 个存活的 cell 时,该 cell 在下一代中仍然存活。
|
||||
if liveCount == 2 || liveCount == 3 {
|
||||
c.aliveNext = true
|
||||
}
|
||||
|
||||
// 3. 当任何一个存活的 cell 的附近多于 3 个存活的 cell 时,该 cell 将会消亡,就像人口过多所导致的结果一样
|
||||
if liveCount > 3 {
|
||||
c.aliveNext = false
|
||||
}
|
||||
} else {
|
||||
// 4. 任何一个消亡的 cell 附近刚好有 3 个存活的 cell,该 cell 会变为存活的状态,就像重生一样。
|
||||
if liveCount == 3 {
|
||||
c.aliveNext = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// liveNeighbors 函数返回当前 cell 附近存活的 cell 数
|
||||
func (c *cell) liveNeighbors(cells [][]*cell) int {
|
||||
var liveCount int
|
||||
add := func(x, y int) {
|
||||
// If we're at an edge, check the other side of the board.
|
||||
if x == len(cells) {
|
||||
x = 0
|
||||
} else if x == -1 {
|
||||
x = len(cells) - 1
|
||||
}
|
||||
if y == len(cells[x]) {
|
||||
y = 0
|
||||
} else if y == -1 {
|
||||
y = len(cells[x]) - 1
|
||||
}
|
||||
|
||||
if cells[x][y].alive {
|
||||
liveCount++
|
||||
}
|
||||
}
|
||||
|
||||
add(c.x-1, c.y) // To the left
|
||||
add(c.x+1, c.y) // To the right
|
||||
add(c.x, c.y+1) // up
|
||||
add(c.x, c.y-1) // down
|
||||
add(c.x-1, c.y+1) // top-left
|
||||
add(c.x+1, c.y+1) // top-right
|
||||
add(c.x-1, c.y-1) // bottom-left
|
||||
add(c.x+1, c.y-1) // bottom-right
|
||||
|
||||
return liveCount
|
||||
}
|
||||
|
||||
// initGlfw 初始化 glfw,返回一个可用的 Window
|
||||
func initGlfw() *glfw.Window {
|
||||
if err := glfw.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
glfw.WindowHint(glfw.Resizable, glfw.False)
|
||||
glfw.WindowHint(glfw.ContextVersionMajor, 4)
|
||||
glfw.WindowHint(glfw.ContextVersionMinor, 1)
|
||||
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
|
||||
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
|
||||
|
||||
window, err := glfw.CreateWindow(width, height, "Conway's Game of Life", nil, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
window.MakeContextCurrent()
|
||||
|
||||
return window
|
||||
}
|
||||
|
||||
// initOpenGL 初始化 OpenGL 并返回一个已经编译好的着色器程序
|
||||
func initOpenGL() uint32 {
|
||||
if err := gl.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
version := gl.GoStr(gl.GetString(gl.VERSION))
|
||||
log.Println("OpenGL version", version)
|
||||
|
||||
vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fragmentShader, err := compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
prog := gl.CreateProgram()
|
||||
gl.AttachShader(prog, vertexShader)
|
||||
gl.AttachShader(prog, fragmentShader)
|
||||
gl.LinkProgram(prog)
|
||||
return prog
|
||||
}
|
||||
|
||||
// makeVao 初始化并从提供的点里面返回一个顶点数组
|
||||
func makeVao(points []float32) uint32 {
|
||||
var vbo uint32
|
||||
gl.GenBuffers(1, &vbo)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
||||
gl.BufferData(gl.ARRAY_BUFFER, 4*len(points), gl.Ptr(points), gl.STATIC_DRAW)
|
||||
|
||||
var vao uint32
|
||||
gl.GenVertexArrays(1, &vao)
|
||||
gl.BindVertexArray(vao)
|
||||
gl.EnableVertexAttribArray(0)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
|
||||
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil)
|
||||
|
||||
return vao
|
||||
}
|
||||
|
||||
func compileShader(source string, shaderType uint32) (uint32, error) {
|
||||
shader := gl.CreateShader(shaderType)
|
||||
|
||||
csources, free := gl.Strs(source)
|
||||
gl.ShaderSource(shader, 1, csources, nil)
|
||||
free()
|
||||
gl.CompileShader(shader)
|
||||
|
||||
var status int32
|
||||
gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
|
||||
if status == gl.FALSE {
|
||||
var logLength int32
|
||||
gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)
|
||||
|
||||
log := strings.Repeat("\x00", int(logLength+1))
|
||||
gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))
|
||||
|
||||
return 0, fmt.Errorf("failed to compile %v: %v", source, log)
|
||||
}
|
||||
|
||||
return shader, nil
|
||||
}
|
||||
```
|
||||
|
||||
请在 Twitter 上告诉我这篇文章对你是否有帮助。
|
||||
|
||||
[@kylewbanks][18]
|
||||
|
||||
或者在 Twitter 下方关注我以便及时获取最新文章!
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
|
||||
作者:[kylewbanks][a]
|
||||
译者:[GitFuture](https://github.com/GitFuture)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://twitter.com/kylewbanks
|
||||
[1]:https://twitter.com/intent/tweet?text=OpenGL%20%26%20Go%20Tutorial%20Part%203%3A%20Implementing%20the%20Game%20https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-3-implementing-the-game%20by%20%40kylewbanks
|
||||
[2]:mailto:?subject=Check%20Out%20%22OpenGL%20%26%20Go%20Tutorial%20Part%203%3A%20Implementing%20the%20Game%22&body=https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
[3]:https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fkylewbanks.com%2Fblog%2Ftutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
[4]:https://github.com/KyleBanks/conways-gol
|
||||
[5]:https://kylewbanks.com/category/golang
|
||||
[6]:https://kylewbanks.com/category/opengl
|
||||
[7]:https://twitter.com/kylewbanks
|
||||
[8]:https://linux.cn/article-8933-1.html
|
||||
[9]:https://linux.cn/article-8937-1.html
|
||||
[10]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
[11]:https://github.com/KyleBanks/conways-gol
|
||||
[12]:https://linux.cn/article-8933-1.html
|
||||
[13]:https://kylewbanks.com/blog/[Part%202:%20Drawing%20the%20Game%20Board](/blog/tutorial-opengl-with-golang-part-2-drawing-the-game-board)
|
||||
[14]:https://linux.cn/article-8933-1.html
|
||||
[15]:https://linux.cn/article-8937-1.html
|
||||
[16]:https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-3-implementing-the-game
|
||||
[17]:https://github.com/KyleBanks/conways-gol
|
||||
[18]:https://twitter.com/kylewbanks
|
||||
@@ -0,0 +1,211 @@
|
||||
介绍 Flashback,一个互联网模拟工具
|
||||
============================================================
|
||||
|
||||
> Flashback 用于测试目的来模拟 HTTP 和 HTTPS 资源,如 Web 服务和 REST API。
|
||||
|
||||

|
||||
|
||||
在 LinkedIn,我们经常开发需要与第三方网站交互的 Web 应用程序。我们还采用自动测试,以确保我们的软件在发布到生产环境之前的质量。然而,测试只是在它可靠时才有用。
|
||||
|
||||
考虑到这一点,有外部依赖关系的测试是有很大的问题的,例如在第三方网站上。这些外部网站可能会没有通知地发生改变、遭受停机,或者由于互联网的不可靠性暂时无法访问。
|
||||
|
||||
如果我们的一个测试依赖于能够与第三方网站通信,那么任何故障的原因都很难确定。失败可能是因为 LinkedIn 的内部变更、第三方网站的维护人员进行的外部变更,或网络基础设施的问题。你可以想像,与第三方网站的交互可能会有很多失败的原因,因此你可能想要知道,我将如何处理这个问题?
|
||||
|
||||
好消息是有许多互联网模拟工具可以帮助。其中一个是 [Betamax][4]。它通过拦截 Web 应用程序发起的 HTTP 连接,之后进行重放的方式来工作。对于测试,Betamax 可以用以前记录的响应替换 HTTP 上的任何交互,它可以非常可靠地提供这个服务。
|
||||
|
||||
最初,我们选择在 LinkedIn 的自动化测试中使用 Betamax。它工作得很好,但我们遇到了一些问题:
|
||||
|
||||
* 出于安全考虑,我们的测试环境没有接入互联网。然而,与大多数代理一样,Betamax 需要 Internet 连接才能正常运行。
|
||||
* 我们有许多需要使用身份验证协议的情况,例如 OAuth 和 OpenId。其中一些协议需要通过 HTTP 进行复杂的交互。为了模拟它们,我们需要一个复杂的模型来捕获和重放请求。
|
||||
|
||||
为了应对这些挑战,我们决定基于 Betamax 的思路,构建我们自己的互联网模拟工具,名为 Flashback。我们也很自豪地宣布 Flashback 现在是开源的。
|
||||
|
||||
### 什么是 Flashback?
|
||||
|
||||
Flashback 用于测试目的来模拟 HTTP 和 HTTPS 资源,如 Web 服务和 [REST][5] API。它记录 HTTP/HTTPS 请求并重放以前记录的 HTTP 事务 - 我们称之为“<ruby>场景<rt>scene</rt></ruby>”,这样就不需要连接到 Internet 才能完成测试。
|
||||
|
||||
Flashback 也可以根据请求的部分匹配重放场景。它使用的是“匹配规则”。匹配规则将传入请求与先前记录的请求相关联,然后将其用于生成响应。例如,以下代码片段实现了一个基本匹配规则,其中测试方法“匹配”[此 URL][6]的传入请求。
|
||||
|
||||
HTTP 请求通常包含 URL、方法、标头和正文。Flashback 允许为这些组件的任意组合定义匹配规则。Flashback 还允许用户向 URL 查询参数,标头和正文添加白名单或黑名单标签。
|
||||
|
||||
例如,在 OAuth 授权流程中,请求查询参数可能如下所示:
|
||||
|
||||
```
|
||||
oauth_consumer_key="jskdjfljsdklfjlsjdfs",
|
||||
oauth_nonce="ajskldfjalksjdflkajsdlfjasldfja;lsdkj",
|
||||
oauth_signature="asdfjaklsdjflasjdflkajsdklf",
|
||||
oauth_signature_method="HMAC-SHA1",
|
||||
oauth_timestamp="1318622958",
|
||||
oauth_token="asdjfkasjdlfajsdklfjalsdjfalksdjflajsdlfa",
|
||||
oauth_version="1.0"
|
||||
```
|
||||
|
||||
这些值许多将随着每个请求而改变,因为 OAuth 要求客户端每次为 `oauth_nonce` 生成一个新值。在我们的测试中,我们需要验证 `oauth_consumer_key`、`oauth_signature_method` 和 `oauth_version` 的值,同时确保 `oauth_nonce`、`oauth_signature`、`oauth_timestamp` 和 `oauth_token` 存在于请求中。Flashback 使我们有能力创建我们自己的匹配规则来实现这一目标。此功能允许我们测试随时间变化的数据、签名、令牌等的请求,而客户端没有任何更改。
|
||||
|
||||
这种灵活的匹配和在不连接互联网的情况下运行的功能是 Flashback 与其他模拟解决方案不同的特性。其他一些显著特点包括:
|
||||
|
||||
* Flashback 是一种跨平台和跨语言解决方案,能够测试 JVM(Java虚拟机)和非 JVM(C++、Python 等)应用程序。
|
||||
* Flashback 可以随时生成 SSL/TLS 证书,以模拟 HTTPS 请求的安全通道。
|
||||
|
||||
### 如何记录 HTTP 事务
|
||||
|
||||
使用 Flashback 记录 HTTP 事务以便稍后重放是一个比较简单的过程。在我们深入了解流程之前,我们首先列出一些术语:
|
||||
|
||||
* `Scene` :场景存储以前记录的 HTTP 事务 (以 JSON 格式),它可以在以后重放。例如,这里是一个[Flashback 场景][1]示例。
|
||||
* `Root Path` :根路径是包含 Flashback 场景数据的目录的文件路径。
|
||||
* `Scene Name` :场景名称是给定场景的名称。
|
||||
* `Scene Mode` :场景模式是使用场景的模式, 即“录制”或“重放”。
|
||||
* `Match Rule` :匹配规则确定传入的客户端请求是否与给定场景的内容匹配的规则。
|
||||
* `Flashback Proxy` :Flashback 代理是一个 HTTP 代理,共有录制和重放两种操作模式。
|
||||
* `Host` 和 `Port` :代理主机和端口。
|
||||
|
||||
为了录制场景,你必须向目的地址发出真实的外部请求,然后 HTTPS 请求和响应将使用你指定的匹配规则存储在场景中。在录制时,Flashback 的行为与典型的 MITM(中间人)代理完全相同 - 只有在重放模式下,连接流和数据流仅限于客户端和代理之间。
|
||||
|
||||
要实际看下 Flashback,让我们创建一个场景,通过执行以下操作捕获与 example.org 的交互:
|
||||
|
||||
1、 取回 Flashback 的源码:
|
||||
|
||||
```
|
||||
git clone https://github.com/linkedin/flashback.git
|
||||
```
|
||||
|
||||
2、 启动 Flashback 管理服务器:
|
||||
|
||||
```
|
||||
./startAdminServer.sh -port 1234
|
||||
```
|
||||
|
||||
3、 注意上面的 Flashback 将在本地端口 5555 上启动录制模式。匹配规则需要完全匹配(匹配 HTTP 正文、标题和 URL)。场景将存储在 `/tmp/test1` 下。
|
||||
|
||||
4、 Flashback 现在可以记录了,所以用它来代理对 example.org 的请求:
|
||||
|
||||
```
|
||||
curl http://www.example.org -x localhost:5555 -X GET
|
||||
```
|
||||
|
||||
5、 Flashback 可以(可选)在一个记录中记录多个请求。要完成录制,[关闭 Flashback][8]。
|
||||
|
||||
6、 要验证已记录的内容,我们可以在输出目录(`/tmp/test1`)中查看场景的内容。它应该[包含以下内容][9]。
|
||||
|
||||
[在 Java 代码中使用 Flashback][10]也很容易。
|
||||
|
||||
### 如何重放 HTTP 事务
|
||||
|
||||
要重放先前存储的场景,请使用与录制时使用的相同的基本设置。唯一的区别是[将“场景模式”设置为上述步骤 3 中的“播放”][11]。
|
||||
|
||||
验证响应来自场景而不是外部源的一种方法,是在你执行步骤 1 到 6 时临时禁用 Internet 连接。另一种方法是修改场景文件,看看响应是否与文件中的相同。
|
||||
|
||||
这是 [Java 中的一个例子][12]。
|
||||
|
||||
### 如何记录并重播 HTTPS 事务
|
||||
|
||||
使用 Flashback 记录并重放 HTTPS 事务的过程非常类似于 HTTP 事务的过程。但是,需要特别注意用于 HTTPS SSL 组件的安全证书。为了使 Flashback 作为 MITM 代理,必须创建证书颁发机构(CA)证书。在客户端和 Flashback 之间创建安全通道时将使用此证书,并允许 Flashback 检查其代理的 HTTPS 请求中的数据。然后将此证书存储为受信任的源,以便客户端在进行调用时能够对 Flashback 进行身份验证。有关如何创建证书的说明,有很多[类似这样][13]的资源是非常有帮助的。大多数公司都有自己的管理和获取证书的内部策略 - 请务必用你们自己的方法。
|
||||
|
||||
这里值得一提的是,Flashback 仅用于测试目的。你可以随时随地将 Flashback 与你的服务集成在一起,但需要注意的是,Flashback 的记录功能将需要存储所有的数据,然后在重放模式下使用它。我们建议你特别注意确保不会无意中记录或存储敏感成员数据。任何可能违反贵公司数据保护或隐私政策的行为都是你的责任。
|
||||
|
||||
一旦涉及安全证书,HTTP 和 HTTPS 之间在记录设置方面的唯一区别是添加了一些其他参数。
|
||||
|
||||
* `RootCertificateInputStream`: 表示 CA 证书文件路径或流。
|
||||
* `RootCertificatePassphrase`: 为 CA 证书创建的密码。
|
||||
* `CertificateAuthority`: CA 证书的属性
|
||||
|
||||
[查看 Flashback 中用于记录 HTTPS 事务的代码][14],它包括上述条目。
|
||||
|
||||
用 Flashback 重放 HTTPS 事务的过程与录制相同。唯一的区别是场景模式设置为“播放”。这在[此代码][15]中演示。
|
||||
|
||||
### 支持动态修改
|
||||
|
||||
为了测试灵活性,Flashback 允许你动态地更改场景和匹配规则。动态更改场景允许使用不同的响应(如 `success`、`time_out`、`rate_limit` 等)测试相同的请求。[场景更改][16]仅适用于我们已经 POST 更新外部资源的场景。以下图为例。
|
||||
|
||||

|
||||
|
||||
能够动态[更改匹配规则][17]可以使我们测试复杂的场景。例如,我们有一个使用情况,要求我们测试 Twitter 的公共和私有资源的 HTTP 调用。对于公共资源,HTTP 请求是不变的,所以我们可以使用 “MatchAll” 规则。然而,对于私人资源,我们需要使用 OAuth 消费者密码和 OAuth 访问令牌来签名请求。这些请求包含大量具有不可预测值的参数,因此静态 MatchAll 规则将无法正常工作。
|
||||
|
||||
### 使用案例
|
||||
|
||||
在 LinkedIn,Flashback 主要用于在集成测试中模拟不同的互联网提供商,如下图所示。第一张图展示了 LinkedIn 生产数据中心内的一个内部服务,通过代理层,与互联网提供商(如 Google)进行交互。我们想在测试环境中测试这个内部服务。
|
||||
|
||||

|
||||
|
||||
第二和第三张图表展示了我们如何在不同的环境中录制和重放场景。记录发生在我们的开发环境中,用户在代理启动的同一端口上启动 Flashback。从内部服务到提供商的所有外部请求将通过 Flashback 而不是我们的代理层。在必要场景得到记录后,我们可以将其部署到我们的测试环境中。
|
||||
|
||||

|
||||
|
||||
在测试环境(隔离并且没有 Internet 访问)中,Flashback 在与开发环境相同的端口上启动。所有 HTTP 请求仍然来自内部服务,但响应将来自 Flashback 而不是 Internet 提供商。
|
||||
|
||||

|
||||
|
||||
### 未来方向
|
||||
|
||||
我们希望将来可以支持非 HTTP 协议(如 FTP 或 JDBC),甚至可以让用户使用 MITM 代理框架来自行注入自己的定制协议。我们将继续改进 Flashback 设置 API,使其更容易支持非 Java 语言。
|
||||
|
||||
### 现在为一个开源项目
|
||||
|
||||
我们很幸运能够在 GTAC 2015 上发布 Flashback。在展会上,有几名观众询问是否将 Flashback 作为开源项目发布,以便他们可以将其用于自己的测试工作。
|
||||
|
||||
### Google TechTalks:GATC 2015 - 模拟互联网
|
||||
|
||||
<iframe allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/6gPNrujpmn0?origin=https://opensource.com&enablejsapi=1" width="560" id="6gPNrujpmn0" data-sdi="true"></iframe>
|
||||
|
||||
我们很高兴地宣布,Flashback 现在以 BSD 两句版许可证开源。要开始使用,请访问 [Flashback GitHub 仓库][18]。
|
||||
|
||||
_该文原始发表在[LinkedIn 工程博客上][2]。获得转载许可_
|
||||
|
||||
### 致谢
|
||||
|
||||
Flashback 由 [Shangshang Feng][19]、[Yabin Kang][20] 和 [Dan Vinegrad][21] 创建,并受到 [Betamax][22] 启发。特别感谢 [Hwansoo Lee][23]、[Eran Leshem][24]、[Kunal Kandekar][25]、[Keith Dsouza][26] 和 [Kang Wang][27] 帮助审阅代码。同样感谢我们的管理层 - [Byron Ma][28]、[Yaz Shimizu][29]、[Yuliya Averbukh][30]、[Christopher Hazlett][31] 和 [Brandon Duncan][32] - 感谢他们在开发和开源 Flashback 中的支持。
|
||||
|
||||
|
||||
(题图:Opensource.com)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
Shangshang Feng - Shangshang 是 LinkedIn 纽约市办公室的高级软件工程师。在 LinkedIn 他从事了三年半的网关平台工作。在加入 LinkedIn 之前,他曾在 Thomson Reuters 和 ViewTrade 证券的基础设施团队工作。
|
||||
|
||||
---------
|
||||
|
||||
via: https://opensource.com/article/17/4/flashback-internet-mocking-tool
|
||||
|
||||
作者:[Shangshang Feng][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[jasminepeng](https://github.com/jasminepeng)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/shangshangfeng
|
||||
[1]:https://gist.github.com/anonymous/17d226050d8a9b79746a78eda9292382
|
||||
[2]:https://engineering.linkedin.com/blog/2017/03/flashback-mocking-tool
|
||||
[3]:https://opensource.com/article/17/4/flashback-internet-mocking-tool?rate=Jwt7-vq6jP9kS7gOT6f6vgwVlZupbyzWsVXX41ikmGk
|
||||
[4]:https://github.com/betamaxteam/betamax
|
||||
[5]:https://en.wikipedia.org/wiki/Representational_state_transfer
|
||||
[6]:https://gist.github.com/anonymous/91637854364287b38897c0970aad7451
|
||||
[7]:https://gist.github.com/anonymous/2f5271191edca93cd2e03ce34d1c2b62
|
||||
[8]:https://gist.github.com/anonymous/f899ebe7c4246904bc764b4e1b93c783
|
||||
[9]:https://gist.github.com/sf1152/c91d6d62518fe62cc87157c9ce0e60cf
|
||||
[10]:https://gist.github.com/anonymous/fdd972f1dfc7363f4f683a825879ce19
|
||||
[11]:https://gist.github.com/anonymous/ae1c519a974c3bc7de2a925254b6550e
|
||||
[12]:https://gist.github.com/anonymous/edcc1d60847d51b159c8fd8a8d0a5f8b
|
||||
[13]:https://jamielinux.com/docs/openssl-certificate-authority/introduction.html
|
||||
[14]:https://gist.github.com/anonymous/091d13179377c765f63d7bf4275acc11
|
||||
[15]:https://gist.github.com/anonymous/ec6a0fd07aab63b7369bf8fde69c1f16
|
||||
[16]:https://gist.github.com/anonymous/1f1660280acb41277fbe2c257bab2217
|
||||
[17]:https://gist.github.com/anonymous/0683c43f31bd916b76aff348ff87f51b
|
||||
[18]:https://github.com/linkedin/flashback
|
||||
[19]:https://www.linkedin.com/in/shangshangfeng
|
||||
[20]:https://www.linkedin.com/in/benykang
|
||||
[21]:https://www.linkedin.com/in/danvinegrad/
|
||||
[22]:https://github.com/betamaxteam/betamax
|
||||
[23]:https://www.linkedin.com/in/hwansoo/
|
||||
[24]:https://www.linkedin.com/in/eranl/
|
||||
[25]:https://www.linkedin.com/in/kunalkandekar/
|
||||
[26]:https://www.linkedin.com/in/dsouzakeith/
|
||||
[27]:https://www.linkedin.com/in/kang-wang-44960b4/
|
||||
[28]:https://www.linkedin.com/in/byronma/
|
||||
[29]:https://www.linkedin.com/in/yazshimizu/
|
||||
[30]:https://www.linkedin.com/in/yuliya-averbukh-818a41/
|
||||
[31]:https://www.linkedin.com/in/chazlett/
|
||||
[32]:https://www.linkedin.com/in/dudcat/
|
||||
[33]:https://opensource.com/user/125361/feed
|
||||
[34]:https://opensource.com/users/shangshangfeng
|
||||
98
published/201710/20170421 A Window Into the Linux Desktop.md
Normal file
98
published/201710/20170421 A Window Into the Linux Desktop.md
Normal file
@@ -0,0 +1,98 @@
|
||||
进入 Linux 桌面之窗
|
||||
============================================================
|
||||
|
||||

|
||||
|
||||
> “它能做什么 Windows 不能做的吗?”
|
||||
|
||||
这是许多人在考虑使用 Linux 桌面时的第一个问题。虽然支撑 Linux 的开源哲学对于某些人来说就是一个很好的理由,但是有些人想知道它在外观、感受和功能上有多么不同。在某种程度上,这取决于你是否选择桌面环境或窗口管理器。
|
||||
|
||||
如果你想要的是闪电般快速的桌面体验且向高效妥协, 那么上述的经典桌面中的窗口管理器可能适合你。
|
||||
|
||||
### 事实之真相
|
||||
|
||||
“<ruby>桌面环境<rt>Desktop Environment</rt></ruby>(DE)”是一个技术术语,指典型的、全功能桌面,即你的操作系统的完整图形化布局。除了显示你的程序,桌面环境还包括应用程序启动器,菜单面板和小部件等组成部分。
|
||||
|
||||
在 Microsoft Windows 中,桌面环境包括开始菜单、显示打开的程序的任务栏和通知中心,还有与操作系统捆绑在一起的所有 Windows 程序,以及围绕这打开的程序的框架(包括右上角的最小按钮、最大按钮和关闭按钮)。
|
||||
|
||||
Linux 中有很多相似之处。
|
||||
|
||||
例如,Linux [Gnome][3] 桌面环境的设计略有不同,但它共享了所有的 Microsoft Windows 的基本元素 - 从应用程序菜单到显示打开的应用程序的面板、通知栏、窗框式程序。
|
||||
|
||||
窗口程序框架依赖于一个组件来绘制它们,并允许你移动并调整大小:它被称为“<ruby>窗口管理器<rt>Window Manager</rt></ruby>(WM)”。因为它们都有窗口,所以每个桌面环境都包含一个窗口管理器。
|
||||
|
||||
然而,并不是每个窗口管理器都是桌面环境的一部分。你可以只运行窗口管理器,并且完全有这么做的需要。
|
||||
|
||||
### 离开你的环境
|
||||
|
||||
对本专栏而言,所谓的“窗口管理器”指的是可以那种独立进行的。如果在现有的 Linux 系统上安装了一个窗口管理器,你可以在不关闭系统的情况下注销,在登录屏幕上选择新的窗口管理器,然后重新登录。
|
||||
|
||||
不过, 在研究你的窗口管理器之前,你可能不想这么做,因为你将会看到一个空白屏幕和稀疏的状态栏,而且它或许能、或许不能点击。
|
||||
|
||||
通常情况下,可以直接在窗口管理器中直接启动终端,因为这是你编辑其配置文件的方式。在那里你会发现用来启动程序的按键和鼠标组合,你实际上也可以使用你的新设置。
|
||||
|
||||
例如,在流行的 i3 窗口管理器中,你可以通过按下 `Super` 键(即 `Windows` 键)加 `Enter` 键来启动终端,或者按 `Super + D` 启动<ruby>应用程序启动器<rt>app launcher</rt></ruby>。你可以在其中输入应用程序名称,然后按 `Enter` 键将其打开。所有已有的应用程序都可以通过这种方式找到,一旦选择后,它们将会全屏打开。
|
||||
|
||||
[][4]
|
||||
|
||||
i3 还是一个平铺式窗口管理器,这意味着它可以确保所有的窗口均匀地扩展到屏幕,既不重叠也不浪费空间。当弹出新窗口时,它会减少现有的窗口,将它们推到一边腾出空间。用户可以以垂直或水平相邻的方式打开下一个窗口。
|
||||
|
||||
### 功能亦敌亦友
|
||||
|
||||
当然,桌面环境有其优点。首先,它们提供功能丰富、可识别的界面。每个都有其特征鲜明的风格,但总体而言,它们提供了普适的默认设置,这使得桌面环境从一开始就可以使用。
|
||||
|
||||
另一个优点是桌面环境带有一组程序和媒体编解码器,允许用户立即完成简单的任务。此外,它们还包括一些方便的功能,如电池监视器、无线小部件和系统通知。
|
||||
|
||||
与桌面环境的完善相应的,是这种大型软件库和用户体验理念独一无二,这就意味着它们所能做的都是有限度的。这也意味着它们并不总是非常可配置。桌面环境强调的是漂亮的外表,很多时候是金玉其外的。
|
||||
|
||||
许多桌面环境对系统资源的渴求是众所周知的,所以它们不太喜欢低端硬件。因为在其上运行的视觉效果,还有更多的东西可能会出错。我曾经尝试调整与我正在运行的桌面环境无关的网络设置,然后整个崩溃了。而当我打开一个窗口管理器,我就可以改变设置。
|
||||
|
||||
那些优先考虑安全性的人可能希望不要桌面环境,因为更多的程序意味着更大的攻击面 —— 也就是坏人可以突破的入口点。
|
||||
|
||||
然而,如果你想尝试一下桌面环境,XFCE 是一个很好的起点,因为它的较小的软件库消除了一些臃肿,如果你不往里面塞东西,垃圾就会更少。
|
||||
|
||||
乍一看,它不是最漂亮的,但在下载了一些 GTK 主题包(每个桌面环境都可以提供这些主题或 Qt 主题,而 XFCE 在 GTK 阵营之中),并且在“外观”部分的设置中,你可以轻松地修改。你甚至可以在这个[集中式画廊][5]中找到你最喜欢的主题。
|
||||
|
||||
### 时间就是生命
|
||||
|
||||
如果你想了解桌面环境之外可以做什么,你会发现窗口管理器给了你足够的回旋余地。
|
||||
|
||||
无论如何,窗口管理器都是与定制有关的。事实上,它们的可定制性已经催生了无数的画廊,承载着一个充满活力的社区用户,他们手中的调色板就是窗口管理器。
|
||||
|
||||
窗口管理器的少量资源需求使它们成为较低规格硬件的理想选择,并且由于大多数窗口管理器不附带任何程序,因此允许喜欢模块化的用户只添加所需的程序。
|
||||
|
||||
可能与桌面环境最为显著的区别是,窗口管理器通常通过鼠标移动和键盘热键来打开程序或启动器来聚焦效率。
|
||||
|
||||
键盘驱动的窗口管理器特别流畅,你可以启动新的窗口、输入文本或更多的键盘命令、移动它们,并再次关闭它们,这一切无需将手从<ruby>键盘中间<rt>home row</rt></ruby>移开。一旦你适应了其设计逻辑,你会惊讶于你能够如此快速地完成任务。
|
||||
|
||||
尽管它们提供了自由,窗口管理器也有其缺点。最显著的是,它们是赤裸裸的开箱即用。在你可以使用其中一个之前,你必须花时间阅读窗口管理器的文档以获取配置语法,可能还需要更多的时间来找到该语法的窍门。
|
||||
|
||||
如果你从桌面环境(这是最可能的情况)切换过来,尽管你会有一些用户程序,你也会缺少一些熟悉的东西,如电池指示器和网络小部件,并且需要一些时间来设置新的。
|
||||
|
||||
如果你想深入窗口管理器,i3 有[完整的文档][6]和简明直白的配置语法。配置文件不使用任何编程语言 - 它只是每行定义一个变量值对。创建热键只要输入 `bindsym`、键盘绑定以及该组合启动的动作即可。
|
||||
|
||||
虽然窗口管理器不适合每个人,但它们提供了独特的计算体验,而 Linux 是少数允许使用它们的操作系统之一。无论你最终采用哪种模式,我希望这个概观能够给你足够的信息,以便对你所做的选择感到自信 —— 或者有足够的信心跨出您熟悉的区域来看看还有什么可用的。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
Jonathan Terrasi - 自 2017 年以来一直是 ECT 新闻网专栏作家。他的主要兴趣是计算机安全(特别是 Linux 桌面)、加密和分析政治和时事。他是全职自由作家和音乐家。他的背景包括在芝加哥委员会发表的关于维护人权法案的文章中提供技术评论和分析。
|
||||
|
||||
-----------
|
||||
|
||||
via: http://www.linuxinsider.com/story/84473.html
|
||||
|
||||
作者:[Jonathan Terrasi][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:
|
||||
[1]:http://www.linuxinsider.com/story/84473.html?rss=1#
|
||||
[2]:http://www.linuxinsider.com/perl/mailit/?id=84473
|
||||
[3]:http://en.wikipedia.org/wiki/GNOME
|
||||
[4]:http://www.linuxinsider.com/article_images/2017/84473_1200x750.jpg
|
||||
[5]:http://www.xfce-look.org/
|
||||
[6]:https://i3wm.org/docs/
|
||||
115
published/201710/20170516 What's the point of DevOps.md
Normal file
115
published/201710/20170516 What's the point of DevOps.md
Normal file
@@ -0,0 +1,115 @@
|
||||
DevOps 的意义
|
||||
========================================
|
||||
|
||||
> 真正的组织文化变革有助于弥合你原以为无法跨过的鸿沟
|
||||
|
||||

|
||||
|
||||
回想一下你最近一次尝试改掉一个个人习惯的事情,你可能遇到过这样的情形,你需要改变你思考的方式并且改掉之前的习惯。这很艰难,你只能试着改变_你自己的_思维方式。
|
||||
|
||||
所以你可能会试着让自己置身于新的环境。新的环境实际上可帮助我们养成_新的_习惯,它反过来又会促成新的思维方式。
|
||||
|
||||
那就是能否成功改变的所在:思考的越多,得到的越多。你需要知道你在改变的原因以及你的目的所在(而不仅仅你要怎么做),因为改变本身往往是短暂和短视的。
|
||||
|
||||
现在想想你的 IT 组织需要做出的改变。也许你正在考虑采用像 DevOps 这样的东西。这个我们称之为 “DevOps” 的东西有三个组件:人、流程和工具。人和流程是_任何_团体组织的基础。因此,采用 DevOps 需要对大多数组织的核心进行根本性的改变,而不仅仅是学习新的工具。
|
||||
|
||||
如同其它的改变一样,它也是短视的。如果您将注意力集中在将改变作为单点解决方案 —— 例如,“获得更好的报警工具” —— 你可能只是管中窥豹。这种思维方式或许可以提供一套拥有更多铃声、口哨以及可以更好地处理呼叫轮询的工具,但是它不能解决这样的实际问题:警报不能送达到正确的团队,或者故障得不到解决,因为实际上没有人知道如何修复服务。
|
||||
|
||||
新的工具(或者至少一个新工具的想法)创造了一个讨论潜在问题的机会,可以让你的团队讨论对监控的看法。新工具让你能够做出更大的改变 —— 信仰和做法的改变 —— 它们作为你组织的基础而显得更加重要。
|
||||
|
||||
创造更深层次的变革需要一种可以全新地改变观念的方法。要找到这种方法,我们首先需要更好的理解变革的驱动力。
|
||||
|
||||
### 清除栅栏
|
||||
|
||||
> 就改革而言,它不同于推翻。这是一条直白且简单的原则,这个原则或许被视作悖论。在这种情况下,存在某种制度或法律;这么说吧,为了简单起见,在一条路上架设了一个栅栏或门。当今的改革者们来到这儿,并说:“我看不到它的用处,让我们把它清除掉。”更聪明的改革者会很好地回答:“如果你看不到它的用处,我肯定不会让你清除它,回去想想,然后你可以回来告诉我你知道了它的用处,我也许会允许你摧毁它。” — G.K Chesterton, 1929
|
||||
|
||||
为了了解对 DevOps 的需求 —— 它试图将传统意义上分开的开发部门和运维部门进行重新组合 —— 我们首先必须明白这个分开是如何产生的。一旦我们"知道了它的用处",然后我们就会知道将它们分开是为了什么,并且在必要的时候可以取消分开。
|
||||
|
||||
今天我们没有一个单一的管理理论,但是大多数现代管理理论的起源可以追溯到<ruby>弗雷德里克·温斯洛·泰勒<rt>Frederick Winslow Taylor</rt></ruby>。泰勒是一名机械工程师,他创建了一个衡量钢厂工人效率的系统。泰勒认为,他可以对工厂的劳动者运用科学分析的方法,不仅可以改进个人任务,也证明发现了有一个可以用来执行_任何_任务最佳方法。
|
||||
|
||||
我们可以很容易地画一个以泰勒为起源的历史树。从泰勒早在 18 世纪 80 年代后期的研究出现的时间运动研究和其他质量改进计划,跨越 20 世纪 20 年代一直到今天,我们可以从中看到六西格玛、精益,等等类似方法。自上而下、指导式管理,再加上研究过程的系统方法,主宰了今天主流商业文化。它主要侧重于把效率作为工人成功的测量标准。
|
||||
|
||||
如果泰勒是我们这颗历史树的根,那么我们主干上的下一个主叉将是 20 世纪 20 年代通用汽车公司的<ruby>阿尔弗雷德·斯隆<rt>Alfred P. Sloan</rt></ruby>。通用汽车公司创造的斯隆结构不仅持续强劲到 21 世纪初,而且在未来五十年的大部分时间里,都将成为该公司的主要模式。
|
||||
|
||||
1920 年,通用公司正经历一场管理危机,或者说是缺乏管理的危机。斯隆向董事会写了一份为通用汽车的多个部门提出了一个新的结构《组织研究》。这一新结构的核心概念是“集中管理下放业务”。与雪佛兰,凯迪拉克和别克等品牌相关的各个部门将独立运作,同时为中央管理层提供推动战略和控制财务的手段。
|
||||
|
||||
在斯隆的建议下(以及后来就任 CEO 的指导下),通用汽车在美国汽车工业中占据了主导地位。斯隆的计划把一个处于灾难边缘公司创造成了一个非常成功的公司。从中间来看,自治单位是黑盒子,激励和目标被设置在顶层,而团队在底层推动。
|
||||
|
||||
泰勒思想的“最佳实践” —— 标准、可互换和可重复的行为 —— 仍然在今天的管理理念中占有一席之地,与斯隆公司结构的层次模式相结合,主导了僵化部门的分裂和孤岛化以实现最大的控制。
|
||||
|
||||
我们可以指出几份管理研究来证明这一点,但商业文化不是通过阅读书籍而创造和传播的。组织文化是 *真实的* 人在 *实际的* 情形下执行推动文化规范的 *具体的* 行为的产物。这就是为何类似泰勒和斯隆这样的理论变得固化而不可动摇的原因。
|
||||
|
||||
技术部门投资就是一个例子。以下是这个周期是如何循环的:投资者只投资于他们认为可以实现 *他们的* 特定成功观点的公司。这个成功的模式并不一定源于公司本身(和它的特定的目标);它来自董事会对一家成功的公司 *应该* 如何看待的想法。许多投资者来自从经营企业的尝试和苦难中幸存下来的公司,因此他们对什么会使一个公司成功有 *不同的* 理念。他们为那些能够被教导模仿他们的成功模式的公司提供资金,希望获得资金的公司学会模仿。这样,初创公司孵化器就是一种重现理想的结构和文化的*直接的*方式。
|
||||
|
||||
“开发”和“运维”的分开不是因为人的原因,也不是因为不同的技能,或者放在新员工头上的一顶魔法分院帽;它是泰勒和斯隆的理论的副产品。责任与人员之间的清晰而不可渗透的界线是一个管理功能,同时也注重员工的工作效率。管理上的分开可以很容易的落在产品或者项目界线上,而不是技能上,但是通过今天的业务管理理论的历史告诉我们,基于技能的分组是“最好”的高效方式。
|
||||
|
||||
不幸的是,那些界线造成了紧张局势,这些紧张局势是由不同的管理链出于不同的目标设定的相反目标的直接结果。例如:
|
||||
|
||||
* 敏捷 ⟷ 稳定
|
||||
* 吸引新用户 ⟷ 现有用户的体验
|
||||
* 让应用程序增加新功能 ⟷ 让应用程序保持可用
|
||||
* 打败竞争对手 ⟷ 维持收入
|
||||
* 修复出现的问题 ⟷ 在问题出现之前就进行预防
|
||||
|
||||
今天,我们可以看到组织的高层领导人越来越认识到,现有的商业文化(并扩大了它所产生的紧张局势)是一个严重的问题。在 2016 年的 Gartner 报告中,57% 的受访者表示,文化变革是 2020 年之前企业面临的主要挑战之一。像作为一种影响组织变革的手段的敏捷和 DevOps 这样的新方法的兴起反映了这一认识。“[影子 IT][7]” 的出现更是事物的另一个方面;最近的估计有将近 30% 的 IT 支出在 IT 组织的控制之外。
|
||||
|
||||
这些只是企业正在面临的一些“文化担忧”。改变的必要性是明确的,但前进的道路仍然受到昨天的决定的约束。
|
||||
|
||||
### 抵抗并不是没用的
|
||||
|
||||
> Bert Lance 认为如果他能让政府采纳一条简单的格言“如果东西还没损坏,那就别去修理它”,他就可以为国家节省三十亿。他解释说:“这是政府的问题:‘修复没有损坏的东西,而不是修复已经损坏了的东西。’” — Nation's Business, 1977.5
|
||||
|
||||
通常,改革是组织针对所出现的错误所做的应对。在这个意义上说,如果紧张局势(即使逆境)是变革的正常催化剂,那么对变化的 *抵抗* 就是成功的指标。但是过分强调成功的道路会使组织变得僵硬、衰竭和独断。重视有效结果的政策导向是这种不断增长的僵局的症状。
|
||||
|
||||
传统 IT 部门的成功加剧了 IT 孤岛的壁垒。其他部门现在变成了“顾客”,而不是伙伴。试图将 IT 从成本中心转移出来创建一个新的操作模式,它可以将 IT 与其他业务目标断开。这反过来又会对敏捷性造成限制,增加摩擦,降低反应能力。合作被搁置转而偏向“专家方向”。结果是一个孤立主义的观点,IT 只能带来更多的伤害而不是好处。
|
||||
|
||||
正如“软件吃掉世界”,IT 越来越成为组织整体成功的核心。具有前瞻性的 IT 组织认识到这一点,并且已经对其战略规划进行了有意义的改变,而不是将改变视为恐惧。
|
||||
|
||||
例如,Facebook 与人类学家<ruby>罗宾·邓巴<rt>Robin Dunbar</rt></ruby>就社会团体的方法进行了磋商,而且意识到这一点对公司成长的内部团体(不仅仅是该网站的外部用户)的影响。<ruby>扎波斯<rt>Zappos</rt></ruby>的文化得到了很多的赞誉,该组织创立了一个部门,专注于培养他人对于核心价值观和企业文化的看法。当然,这本书是 《开放式组织》的姊妹篇,那是一本描述被应用于管理的开放原则 —— 透明度、参与度和社区 —— 可以如何为我们快节奏的互联时代重塑组织。
|
||||
|
||||
### 决心改变
|
||||
|
||||
> “如果外界的变化率超过了内部的变化率,那末日就不远了。” — Jack Welch, 2004
|
||||
|
||||
一位同事曾经告诉我他可以只用 “[信息技术基础设施库(ITIL)][9]” 框架里面的词汇向一位项目经理解释 DevOps。
|
||||
|
||||
虽然这些框架 *似乎* 是反面的,但实际上它们都集中在风险和变革管理上。它们简单地介绍了这种管理的不同流程和工具。在 IT 圈子外面谈论 DevOps 时,这一点需要注意。不要强调流程问题和故障,而是显示更小的变化引起的风险 *更小* 等等。这是强调改变团队文化的有益方式:专注于 *新的* 功能而不是老问题,是改变的有效中介,特别是当您采用别人的框架进行参考时。
|
||||
|
||||
改革不仅仅只是 *重构* 组织;它也是跨越历史上不可跨越的鸿沟的新途径 —— 通过拒绝把像“敏捷”和“稳定”这样的东西作为互相排斥的力量来解决我之前提到的那些紧张局势。建立注重 *结果* 胜过 *功能* 的跨部门团队是一个有效的策略。把不同的团队 —— 其中每个人的工作依赖于其他人 —— 聚集起来围绕一个项目或目标是最常见的方法之一。消除这些团队之间的摩擦和改善沟通能够产生巨大的改进 —— 即使在坚持铁仓管理结构时(如果可以掌握,则不需要拆除孤岛)。在这些情况下,对改革的 *抵抗* 不是成功的一个指标;而对改革的拥抱才是。
|
||||
|
||||
这些也不是“最佳实例”,它们只是一种检查你自己的栅栏的方式。每个组织都会有独特的、由他们内部人员创造的栅栏。一旦你“知道了它的用途”,你就可以决定它是需要拆解还是掌握。
|
||||
|
||||
** 本文是 Opensource.com 即将推出的关于开放组织和 IT 文化指南的一部分。[你可以在这注册以便当它发布时收到通知][5]**
|
||||
|
||||
(题图 : opensource.com)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
作者简介:
|
||||
|
||||
Matt Micene 是 Red Hat 公司的 Linux 和容器传播者。从架构和系统设计到数据中心设计,他在信息技术方面拥有超过 15 年的经验。他对关键技术(如容器,云计算和虚拟化)有深入的了解。他目前的重点是宣传红帽企业版 Linux,以及操作系统如何与计算环境的新时代相关。
|
||||
|
||||
------------------------------------------
|
||||
|
||||
via: https://opensource.com/open-organization/17/5/what-is-the-point-of-DevOps
|
||||
|
||||
作者:[Matt Micene][a]
|
||||
译者:[zhousiyu325](https://github.com/zhousiyu325)
|
||||
校对:[apemost](https://github.com/apemost),[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/matt-micene
|
||||
[1]:https://opensource.com/open-organization/resources/leaders-manual?src=too_resource_menu
|
||||
[2]:https://opensource.com/open-organization/resources/field-guide?src=too_resource_menu
|
||||
[3]:https://opensource.com/open-organization/resources/open-org-definition?src=too_resource_menu
|
||||
[4]:https://opensource.com/open-organization/resources/open-decision-framework?src=too_resource_menu
|
||||
[5]:https://opensource.com/open-organization/resources/book-series
|
||||
[6]:https://opensource.com/open-organization/17/5/what-is-the-point-of-DevOps?rate=gOQvGqsEbNk_RSnoU0wP3PJ71E_XDYiYo7KS2HKFfP0
|
||||
[7]:https://thenewstack.io/parity-check-dont-afraid-shadow-yet/
|
||||
[8]:http://www.npr.org/2017/01/13/509358157/is-there-a-limit-to-how-many-friends-we-can-have
|
||||
[9]:https://en.wikipedia.org/wiki/ITIL
|
||||
[10]:https://opensource.com/user/18066/feed
|
||||
[11]:https://opensource.com/open-organization/17/5/what-is-the-point-of-DevOps#comments
|
||||
[12]:https://opensource.com/users/matt-micene
|
||||
187
published/201710/20170529 LFCS sed Command.md
Normal file
187
published/201710/20170529 LFCS sed Command.md
Normal file
@@ -0,0 +1,187 @@
|
||||
LFCS 基础:sed 命令
|
||||
=====================
|
||||
|
||||
Linux 基金会认证系统管理员(LFCS)的另一个有用的命令是 “sed”,最初表示“流式编辑器” (Streaming EDitor)。
|
||||
|
||||
“sed” 命令是一个可以将文件作为流进行编辑的编辑器。流式传输文件的方法是从另一个命令使用管道(`>` 或 `|`)传递,或将其直接加载到 “sed” 中。
|
||||
|
||||
该命令的工作方式与其他编辑器相同,只是文件不显示,也不允许可视化编辑。命令被传递给 “sed” 来操纵流。
|
||||
|
||||
用 “sed” 可以做五件基本的事。当然,“sed” 如此强大,还有其他高级的功能,但你只需要集中精力在五件基本的事上。五种功能类型如下:
|
||||
|
||||
1. 搜索
|
||||
2. 替换
|
||||
3. 删除
|
||||
4. 添加
|
||||
5. 改变/变换
|
||||
|
||||
在深入命令参数之前,我们需要看看基本的语法。
|
||||
|
||||
### 语法
|
||||
|
||||
“sed” 命令的语法是:
|
||||
|
||||
```
|
||||
sed [选项] 命令 [要编辑的文件]
|
||||
```
|
||||
|
||||
本文将在适当的部分中介绍这些“选项”。“命令”可以是正则表达式的搜索和替换模式。请继续阅读了解 “sed” 如何工作的并学习基本命令。正如我之前提到的,“sed” 是一个非常强大的工具,有更多的选项可用,我将在本文中介绍。
|
||||
|
||||
### 示例文件
|
||||
|
||||
如果你打开一个终端,那你可以创建一个用于 “sed” 示例的文件。执行以下命令:
|
||||
|
||||
```
|
||||
cd ~
|
||||
grep --help >grephelp.txt
|
||||
```
|
||||
|
||||
你现在应该在 HOME 文件夹中有一个名为 `grephelp.txt` 的文件。该文件的内容是 `grep` 命令的帮助说明。
|
||||
|
||||
### 搜索
|
||||
|
||||
搜索特定字符串是编辑器的常见功能,在 “sed” 中执行搜索也不例外。
|
||||
|
||||
执行搜索以在文件中查找字符串。我们来看一下基本的搜索。
|
||||
|
||||
如果我们想在示例文件搜索 `PATTERN` 这个词,我们将使用如下命令:
|
||||
|
||||
```
|
||||
sed -n 's/PATTERN/PATTERN/p' grephelp.txt
|
||||
```
|
||||
|
||||
**注意:** 如果剪切粘贴命令,请确保将单引号替换为键盘上的标准单引号。
|
||||
|
||||
参数 `-n` 用于抑制每行的自动打印(除了用 `p` 命令指定的行)。默认情况下,流入 “sed” 的每一行将被打印到标准输出(stdout)。如果你不使用 “-n” 选项运行上述命令,你将看到原始文件的每一行以及匹配的行。
|
||||
|
||||
要搜索的文件名是我们在“示例文件”部分中创建的 “grephelp.txt”。
|
||||
|
||||
剩下的部分是 `'s/PATTERN/PATTERN/p'` 。这一段基本分为四个部分。第一部分的 `s` 指定执行替换,或搜索并替换。
|
||||
|
||||
剩下的第二部分和第三部分是模式。第一个是要搜索的模式,最后一个是替换流中匹配字符串的模式。此例中,我们找到字符串 `PATTERN`,并用 `PATTERN` 替换。通过查找和替换相同的字符串,我们完全不会更改文件,甚至在屏幕上也一样。
|
||||
|
||||
最后一个命令是 `p`。 它指定在替换后打印新行。当然,因为替换的是相同的字符串,所以没有改变。由于我们使用 `-n` 参数抑制打印行,所以更改的行将使用 `p` 命令打印。
|
||||
|
||||
这个完整的命令允许我们执行搜索并查看匹配的结果。
|
||||
|
||||
### 替换
|
||||
|
||||
当搜索特定字符串时,你可能希望用匹配的字符串替换新字符串。用另一个字符串替换是很常见的操作。
|
||||
|
||||
我们可以使用以下命令执行相同的搜索:
|
||||
|
||||
```
|
||||
sed -n 's/PATTERN/Pattern/p' grephelp.txt
|
||||
```
|
||||
|
||||
在这时,字符串 “PATTERN” 变为 “Pattern” 并显示。如果你使用命令 `cat grephelp.txt` 查看文件,你会看到该文件没有更改。该更改仅对屏幕上的输出进行。你可以使用以下命令将输出通过管道传输到另一个文件:
|
||||
|
||||
```
|
||||
sed 's/PATTERN/Pattern/' grephelp.txt > grephelp1.txt
|
||||
```
|
||||
|
||||
现在将存在一个名为 `grephelp1.txt` 的新文件,其中保存了更改的文件。如果 `p` 作为第四个选项留下,那么有个问题是被替换的字符串的每一行将在文件中重复两次。我们也可以删除 “-n” 参数以允许所有的行打印。
|
||||
|
||||
使用相同字符串替换字符串的另一种方法是使用 `&` 符号来表示搜索字符串。例如,命令 `s/PATTERN/&/p` 效果是一样的。我们可以添加字符串,例如添加 `S`,可以使用命令 `s/PATTERN/&S/p`。
|
||||
|
||||
如果我们希望在每一行中只替换某种模式呢?可以指定要替换的匹配项的特定出现。当然,每一行的替换都是一个特定的编号。例如,示例文件上有很多破折号。一些行至少有两条破折号,所以我们可以用另一个字符代替每一行的第二个破折号。每行用星号 `*` 替换第二个破折号 `-` 的命令将是:
|
||||
|
||||
```
|
||||
sed 's/-/*/2' grephelp.txt
|
||||
```
|
||||
|
||||
在这里,我们用最初的 `s` 来执行替换。字符 `-` 被替换为 `*`。`2` 表示我们想要替换每行上的第二个 `-`(如果存在)。如果我们忽略了命令 `2`,则替换第一次出现的破折号。只有第一个破折号而不是每行的破折号都被替换。
|
||||
|
||||
如果要搜索并替换带有星号的行上的所有破折号,请使用 `g` 命令:
|
||||
|
||||
```
|
||||
sed 's/-/*/g' grephelp.txt
|
||||
```
|
||||
|
||||
命令也可以组合。假设你想要替换从第二次开始出现的破折号,命令将是:
|
||||
|
||||
```
|
||||
sed 's/-/*/2g' grephelp.txt
|
||||
```
|
||||
|
||||
现在从第二个开始出现的破折号将被星号取代。
|
||||
|
||||
### 删除
|
||||
|
||||
搜索过程中有很多时候你可能想要完全删除搜索字符串。
|
||||
|
||||
例如,如果要从文件中删除所有破折号,你可以使用以下命令:
|
||||
|
||||
```
|
||||
sed 's/-//g' grephelp.txt
|
||||
```
|
||||
|
||||
替换字符串为空白,因此匹配的字符串将被删除。
|
||||
|
||||
### 添加
|
||||
|
||||
当找到匹配时,你可以添加一行特定的文本,来使这行在浏览或打印中突出。
|
||||
|
||||
如果要在匹配后插入新行,那么使用 `a` 命令,后面跟上新行的字符串。还包括要匹配的字符串。例如,我们可以找到一个 `--`,并在匹配的行之后添加一行。新行的字符串将是 `double dash before this line`。
|
||||
|
||||
```
|
||||
sed '/--/ a "double dash before this line"' grephelp.txt
|
||||
```
|
||||
|
||||
如果要在包含匹配字符串的行之前加上这行,请使用 `i` 命令,如下所示:
|
||||
|
||||
```
|
||||
sed '/--/ i "double dash after this line"' grephelp.txt
|
||||
```
|
||||
|
||||
### 改变/变换
|
||||
|
||||
如果需要改变/变换一行,则可以使用命令 `c`。
|
||||
|
||||
假设我们有个有一些私人信息的文档,我们需要更改包含特定字符串的行。`c` 命令将改变整行,而不仅仅是搜索字符串。
|
||||
|
||||
假设我们想要阻止示例文件中包含单词 `PATTERN` 的每一行。更改的行将显示为 `This line is Top Secret`。命令是:
|
||||
|
||||
```
|
||||
sed '/PATTERN/ c This line is Top Secret' grephelp.txt
|
||||
```
|
||||
|
||||
可以进行更改特定字母的大小写的转换。例如,我们可以使用命令 `y` 将所有小写 `a` 更改为大写 `A`,如下所示:
|
||||
|
||||
```
|
||||
sed 'y/a/A/' grephelp.txt
|
||||
```
|
||||
|
||||
可以指定多个字母,如 `abdg`,如下命令所示:
|
||||
|
||||
```
|
||||
sed 'y/abdg/ABDG/' grephelp.txt
|
||||
```
|
||||
|
||||
确保第二组字母与第一组字母的顺序相同,否则会被替换和转换。例如,字符串 `y/a/D/` 将用大写 `D` 替换所有小写的 `a`。
|
||||
|
||||
### 就地更改
|
||||
|
||||
如果你确实要更改所使用的文件,请使用 `-i` 选项。
|
||||
|
||||
例如,要将 `PATTERN` 改为 `Pattern`,并对文件进行更改,则命令为:
|
||||
|
||||
```
|
||||
sed -i 's/PATTERN/Pattern/' grephelp.txt
|
||||
```
|
||||
|
||||
现在文件 `grephelp.txt` 将被更改。`-i` 选项可以与上述任何命令一起使用来更改原始文件的内容。
|
||||
|
||||
练习这些命令,并确保你理解它们。“sed” 命令非常强大。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.linux.org/threads/lfcs-sed-command.4561/
|
||||
|
||||
作者:[Jarret B][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.linux.org/threads/lfcs-sed-command.4561/
|
||||
@@ -0,0 +1,106 @@
|
||||
当你只想将事情搞定时,为什么开放式工作这么难?
|
||||
============================================================
|
||||
|
||||
> 学习使用开放式决策框架来写一本书
|
||||
|
||||

|
||||
|
||||
GSD(get stuff done 的缩写,即搞定)指导着我的工作方式。数年来,我将各种方法论融入我日常工作的习惯中,包括精益方法的反馈循环,和敏捷开发的迭代优化,以此来更好地 GSD(如果把 GSD 当作动词的话)。这意味着我必须非常有效地利用我的时间:列出清晰、各自独立的目标;标记已完成的项目;用迭代的方式地持续推进项目进度。但是当我们以开放为基础时仍然能够 GSD 吗?又或者 GSD 的方法完全行不通呢?大多数人都认为这会导致糟糕的状况,但我发现事实并不一定这样。
|
||||
|
||||
在开放的环境中工作,遵循<ruby>[开放式决策框架][6]<rt>Open Decision Framework</rt></ruby>中的指导,会让项目起步变慢。但是在最近的一个项目中,我们作出了一个决定,一个从开始就正确的决定:以开放的方式工作,并与我们的社群一起合作。
|
||||
|
||||
这是我们能做的最好的决定。
|
||||
|
||||
我们来看看这次经历带来的意想不到的结果,再看看我们如何将 GSD 思想融入开放式组织框架。
|
||||
|
||||
### 建立社区
|
||||
|
||||
2014 年 10 月,我接手了一个新的项目:当时红帽的 CEO Jim Whitehurst 即将推出一本新书<ruby>《开放式组织》<rt>The Open Organization</rt></ruby>,我要根据书中提出的概念,建立一个社区。“太棒了,这听起来是一个挑战,我加入了!”我这样想。但不久,[冒牌者综合征][7]便出现了,我又开始想:“我们究竟要做什么呢?怎样才算成功呢?”
|
||||
|
||||
让我剧透一下,在这本书的结尾处,Jim 鼓励读者访问 Opensource.com,继续探讨 21 世纪的开放和管理。所以,在 2015 年 5 月,我们的团队在网站上建立了一个新的板块来讨论这些想法。我们计划讲一些故事,就像我们在 Opensource.com 上常做的那样,只不过这次围绕着书中的观点与概念。之后,我们每周都发布新的文章,在 Twitter 上举办了一个在线的读书俱乐部,还将《开放式组织》打造成了系列书籍。
|
||||
|
||||
我们内部独自完成了该系列书籍的前三期,每隔六个月发布一期。每完成一期,我们就向社区发布。然后我们继续完成下一期的工作,如此循环下去。
|
||||
|
||||
这种工作方式,让我们看到了很大的成功。近 3000 人订阅了[该系列的新书][9]:《开放式组织领袖手册》。我们用 6 个月的周期来完成这个项目,这样新书的发行日正好是前书的两周年纪念日。
|
||||
|
||||
在这样的背景下,我们完成这本书的方式是简单直接的:针对开放工作这个主题,我们收集了最好的故事,并将它们组织起来形成文章,招募作者填补一些内容上的空白,使用开源工具调整字体样式,与设计师一起完成封面,最终发布这本书。这样的工作方式使得我们能按照自己的时间线(GSD)全速前进。到[第三本书][10]时,我们的工作流已经基本完善了。
|
||||
|
||||
然而这一切在我们计划开始《开放式组织》的最后一本书时改变了,这本书将重点放在开放式组织和 IT 文化的交融上。我提议使用开放式决策框架来完成这本书,因为我想通过这本书证明开放式的工作方法能得到更好的结果,尽管我知道这可能会完全改变我们的工作方式。时间非常紧张(只有两个半月),但我们还是决定试一试。
|
||||
|
||||
### 用开放式决策框架来完成一本书
|
||||
|
||||
开放式决策框架列出了组成开放决策制定过程的 4 个阶段。下面是我们在每个阶段中的工作情况(以及开放是如何帮助完成工作的)。
|
||||
|
||||
#### 1、 构思
|
||||
|
||||
我们首先写了一份草稿,罗列了对项目设想的愿景。我们需要拿出东西来和潜在的“顾客”分享(在这个例子中,“顾客”指潜在的利益相关者和作者)。然后我们约了一些领域专家面谈,这些专家能够给我们直接的诚实的意见。这些专家表现出的热情与他们提供的指导验证了我们的想法,同时提出了反馈意见使我们能继续向前。如果我们没有得到这些验证,我们会退回到我们最初的想法,再决定从哪里重新开始。
|
||||
|
||||
#### 2、 计划与研究
|
||||
|
||||
经过几次面谈,我们准备在 [Opensource.com 上公布这个项目][11]。同时,我们在 [Github 上也启动了这个项目][12],提供了项目描述、预计的时间线,并阐明了我们所受的约束。这次公布得到了很好的效果,我们最初计划的目录中欠缺了一些内容,在项目公布之后的 72 小时内就被补充完整了。另外(也是更重要的),读者针对一些章节,提出了本不在我们计划中的想法,但是读者觉得这些想法能够补充我们最初设想的版本。
|
||||
|
||||
回顾过去,我觉得在项目的第一和第二个阶段,开放项目并不会影响我们搞定项目的能力。事实上,这样工作有一个很大的好处:发现并填补内容的空缺。我们不只是填补了空缺,我们是迅速地填补了空缺,并且还是用我们自己从未考虑过的点子。这并不一定要求我们做更多的工作,只是改变了我们的工作方式。我们动用有限的人脉,邀请别人来写作,再组织收到的内容,设置上下文,将人们导向正确的方向。
|
||||
|
||||
#### 3、 设计,开发和测试
|
||||
|
||||
项目的这个阶段完全围绕项目管理,管理一些像猫一样特立独行的人,并处理项目的预期。我们有明确的截止时间,我们提前沟通,频繁沟通。我们还使用了一个战略:列出了贡献者和利益相关者,在项目的整个过程中向他们告知项目的进度,尤其是我们在 Github 上标出的里程碑。
|
||||
|
||||
最后,我们的书需要一个名字。我们收集了许多反馈,指出书名应该是什么,更重要的是反馈指出了书名不应该是什么。我们通过 [Github 上的工单][13]收集反馈意见,并公开表示我们的团队将作最后的决定。当我们准备宣布最后的书名时,我的同事 Bryan Behrenshausen 做了很好的工作,[分享了我们作出决定的过程][14]。人们似乎对此感到高兴——即使他们不同意我们最后的书名。
|
||||
|
||||
书的“测试”阶段需要大量的[校对][15]。社区成员真的参与到回答这个“求助”贴中来。我们在 GitHub 工单上收到了大约 80 条意见,汇报校对工作的进度(更不用说通过电子邮件和其他反馈渠道获得的许多额外的反馈)。
|
||||
|
||||
关于搞定任务:在这个阶段,我们亲身体会了 [Linus 法则][16]:“<ruby>众目之下,_笔误_无所遁形。<rt>With more eyes, all _typos_ are shallow.</rt></ruby>” 如果我们像前三本书一样自己独立完成,那么整个校对的负担就会落在我们的肩上(就像这些书一样)!相反,社区成员慷慨地帮我们承担了校对的重担,我们的工作从自己校对(尽管我们仍然做了很多工作)转向管理所有的 change requests。对我们团队来说,这是一个受大家欢迎的改变;对社区来说,这是一个参与的机会。如果我们自己做的话,我们肯定能更快地完成校对,但是在开放的情况下,我们在截止日期之前发现了更多的错误,这一点毋庸置疑。
|
||||
|
||||
#### 4、 发布
|
||||
|
||||
好了,我们现在推出了这本书的最终版本。(或者只是第一版?)
|
||||
|
||||
我们把发布分为两个阶段。首先,根据我们的公开的项目时间表,在最终日期之前的几天,我们安静地推出了这本书,以便让我们的社区贡献者帮助我们测试[下载表格][17]。第二阶段也就是现在,这本书的[通用版][18]的正式公布。当然,我们在发布后的仍然接受反馈,开源方式也正是如此。
|
||||
|
||||
### 成就解锁
|
||||
|
||||
遵循开放式决策框架是<ruby>《IT 文化变革指南》<rt>Guide to IT Culture Change</rt></ruby>成功的关键。通过与客户和利益相关者的合作,分享我们的制约因素,工作透明化,我们甚至超出了自己对图书项目的期望。
|
||||
|
||||
我对整个项目中的合作,反馈和活动感到非常满意。虽然有一段时间内没有像我想要的那样快速完成任务,这让我有一种焦虑感,但我很快就意识到,开放这个过程实际上让我们能完成更多的事情。基于上面我的概述这一点显而易见。
|
||||
|
||||
所以也许我应该重新考虑我的 GSD 心态,并将其扩展到 GMD:Get **More** Done,搞定**更多**工作,并且就这个例子说,取得更好的结果。
|
||||
|
||||
(题图:opensource.com)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
Jason Hibbets - Jason Hibbets 是 Red Hat 企业营销中的高级社区传播者,也是 Opensource.com 的社区经理。 他自2003 年以来一直在 Red Hat,并且是开源城市基金会的创立者。之前的职位包括高级营销专员、项目经理、Red Hat 知识库维护人员和支持工程师。
|
||||
|
||||
-----------
|
||||
|
||||
via: https://opensource.com/open-organization/17/6/working-open-and-gsd
|
||||
|
||||
作者:[Jason Hibbets][a]
|
||||
译者:[explosic4](https://github.com/explosic4)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/jhibbets
|
||||
[1]:https://opensource.com/open-organization/resources/culture-change?src=too_resource_menu
|
||||
[2]:https://opensource.com/open-organization/resources/leaders-manual?src=too_resource_menu
|
||||
[3]:https://opensource.com/open-organization/resources/open-org-definition?src=too_resource_menu
|
||||
[4]:https://opensource.com/open-organization/resources/open-decision-framework?src=too_resource_menu
|
||||
[5]:https://opensource.com/open-organization/17/6/working-open-and-gsd?rate=ZgpGc0D07SjGkTOf708lnNqbF_HvkhXTXeSzRKMhvVM
|
||||
[6]:https://opensource.com/open-organization/resources/open-decision-framework
|
||||
[7]:https://opensource.com/open-organization/17/5/team-impostor-syndrome
|
||||
[8]:https://opensource.com/open-organization/resources
|
||||
[9]:https://opensource.com/open-organization/resources/leaders-manual
|
||||
[10]:https://opensource.com/open-organization/resources/leaders-manual
|
||||
[11]:https://opensource.com/open-organization/17/3/announcing-it-culture-book
|
||||
[12]:https://github.com/open-organization-ambassadors/open-org-it-culture
|
||||
[13]:https://github.com/open-organization-ambassadors/open-org-it-culture/issues/20
|
||||
[14]:https://github.com/open-organization-ambassadors/open-org-it-culture/issues/20#issuecomment-297970303
|
||||
[15]:https://github.com/open-organization-ambassadors/open-org-it-culture/issues/29
|
||||
[16]:https://en.wikipedia.org/wiki/Linus%27s_Law
|
||||
[17]:https://opensource.com/open-organization/resources/culture-change
|
||||
[18]:https://opensource.com/open-organization/resources/culture-change
|
||||
[19]:https://opensource.com/user/10530/feed
|
||||
[20]:https://opensource.com/users/jhibbets
|
||||
@@ -0,0 +1,311 @@
|
||||
关于 HTML5 你需要了解的基础知识
|
||||
============================================================
|
||||
|
||||

|
||||
|
||||
> HTML5 是第五个且是当前的 HTML 版本,它是用于在万维网上构建和呈现内容的标记语言。本文将帮助读者了解它。
|
||||
|
||||
HTML5 通过 W3C 和<ruby>Web 超文本应用技术工作组<rt>Web Hypertext Application Technology Working Group</rt></ruby>之间的合作发展起来。它是一个更高版本的 HTML,它的许多新元素可以使你的页面更加语义化和动态。它是为所有人提供更好的 Web 体验而开发的。HTML5 提供了很多的功能,使 Web 更加动态和交互。
|
||||
|
||||
HTML5 的新功能是:
|
||||
|
||||
* 新标签,如 `<header>` 和 `<section>`
|
||||
* 用于 2D 绘图的 `<canvas>` 元素
|
||||
* 本地存储
|
||||
* 新的表单控件,如日历、日期和时间
|
||||
* 新媒体功能
|
||||
* 地理位置
|
||||
|
||||
HTML5 还不是正式标准(LCTT 译注:HTML5 已于 2014 年成为“推荐标准”),因此,并不是所有的浏览器都支持它或其中一些功能。开发 HTML5 背后最重要的原因之一是防止用户下载并安装像 Silverlight 和 Flash 这样的多个插件。
|
||||
|
||||
**新标签和元素**
|
||||
|
||||
- **语义化元素:** 图 1 展示了一些有用的语义化元素。
|
||||
- **表单元素:** HTML5 中的表单元素如图 2 所示。
|
||||
- **图形元素:** HTML5 中的图形元素如图 3 所示。
|
||||
- **媒体元素:** HTML5 中的新媒体元素如图 4 所示。
|
||||
|
||||
|
||||
[][3]
|
||||
|
||||
*图 1:语义化元素*
|
||||
|
||||
[][4]
|
||||
|
||||
*图 2:表单元素*
|
||||
|
||||
[][5]
|
||||
|
||||
*图 3:图形元素*
|
||||
|
||||
[][6]
|
||||
|
||||
*图 4:媒体元素*
|
||||
|
||||
### HTML5 的高级功能
|
||||
|
||||
#### 地理位置
|
||||
|
||||
这是一个 HTML5 API,用于获取网站用户的地理位置,用户必须首先允许网站获取他或她的位置。这通常通过按钮和/或浏览器弹出窗口来实现。所有最新版本的 Chrome、Firefox、IE、Safari 和 Opera 都可以使用 HTML5 的地理位置功能。
|
||||
|
||||
地理位置的一些用途是:
|
||||
|
||||
* 公共交通网站
|
||||
* 出租车及其他运输网站
|
||||
* 电子商务网站计算运费
|
||||
* 旅行社网站
|
||||
* 房地产网站
|
||||
* 在附近播放的电影的电影院网站
|
||||
* 在线游戏
|
||||
* 网站首页提供本地标题和天气
|
||||
* 工作职位可以自动计算通勤时间
|
||||
|
||||
**工作原理:** 地理位置通过扫描位置信息的常见源进行工作,其中包括以下:
|
||||
|
||||
* 全球定位系统(GPS)是最准确的
|
||||
* 网络信号 - IP地址、RFID、Wi-Fi 和蓝牙 MAC地址
|
||||
* GSM/CDMA 蜂窝 ID
|
||||
* 用户输入
|
||||
|
||||
该 API 提供了非常方便的函数来检测浏览器中的地理位置支持:
|
||||
|
||||
```
|
||||
if (navigator.geolocation) {
|
||||
// do stuff
|
||||
}
|
||||
```
|
||||
`getCurrentPosition` API 是使用地理位置的主要方法。它检索用户设备的当前地理位置。该位置被描述为一组地理坐标以及航向和速度。位置信息作为位置对象返回。
|
||||
|
||||
语法是:
|
||||
|
||||
```
|
||||
getCurrentPosition(showLocation, ErrorHandler, options);
|
||||
```
|
||||
|
||||
* `showLocation`:定义了检索位置信息的回调方法。
|
||||
* `ErrorHandler`(可选):定义了在处理异步调用时发生错误时调用的回调方法。
|
||||
* `options` (可选): 定义了一组用于检索位置信息的选项。
|
||||
|
||||
我们可以通过两种方式向用户提供位置信息:测地和民用。
|
||||
|
||||
1. 描述位置的测地方式直接指向纬度和经度。
|
||||
2. 位置信息的民用表示法是人类可读的且容易理解。
|
||||
|
||||
如下表 1 所示,每个属性/参数都具有测地和民用表示。
|
||||
|
||||
[][7]
|
||||
|
||||
图 5 包含了一个位置对象返回的属性集。
|
||||
|
||||
[][8]
|
||||
|
||||
*图5:位置对象属性*
|
||||
|
||||
#### 网络存储
|
||||
|
||||
在 HTML 中,为了在本机存储用户数据,我们需要使用 JavaScript cookie。为了避免这种情况,HTML5 已经引入了 Web 存储,网站利用它在本机上存储用户数据。
|
||||
|
||||
与 Cookie 相比,Web 存储的优点是:
|
||||
|
||||
* 更安全
|
||||
* 更快
|
||||
* 存储更多的数据
|
||||
* 存储的数据不会随每个服务器请求一起发送。只有在被要求时才包括在内。这是 HTML5 Web 存储超过 Cookie 的一大优势。
|
||||
|
||||
有两种类型的 Web 存储对象:
|
||||
|
||||
1. 本地 - 存储没有到期日期的数据。
|
||||
2. 会话 - 仅存储一个会话的数据。
|
||||
|
||||
**如何工作:** `localStorage` 和 `sessionStorage` 对象创建一个 `key=value` 对。比如: `key="Name"`,` value="Palak"`。
|
||||
|
||||
这些存储为字符串,但如果需要,可以使用 JavaScript 函数(如 `parseInt()` 和 `parseFloat()`)进行转换。
|
||||
|
||||
下面给出了使用 Web 存储对象的语法:
|
||||
|
||||
- 存储一个值:
|
||||
- `localStorage.setItem("key1", "value1");`
|
||||
- `localStorage["key1"] = "value1";`
|
||||
- 得到一个值:
|
||||
- `alert(localStorage.getItem("key1"));`
|
||||
- `alert(localStorage["key1"]);`
|
||||
- 删除一个值:
|
||||
-`removeItem("key1");`
|
||||
- 删除所有值:
|
||||
- `localStorage.clear();`
|
||||
|
||||
#### 应用缓存(AppCache)
|
||||
|
||||
使用 HTML5 AppCache,我们可以使 Web 应用程序在没有 Internet 连接的情况下脱机工作。除 IE 之外,所有浏览器都可以使用 AppCache(截止至此时)。
|
||||
|
||||
应用缓存的优点是:
|
||||
|
||||
* 网页浏览可以脱机
|
||||
* 页面加载速度更快
|
||||
* 服务器负载更小
|
||||
|
||||
`cache manifest` 是一个简单的文本文件,其中列出了浏览器应缓存的资源以进行脱机访问。 `manifest` 属性可以包含在文档的 HTML 标签中,如下所示:
|
||||
|
||||
```
|
||||
<html manifest="test.appcache">
|
||||
...
|
||||
</html>
|
||||
```
|
||||
|
||||
它应该在你要缓存的所有页面上。
|
||||
|
||||
缓存的应用程序页面将一直保留,除非:
|
||||
|
||||
1. 用户清除它们
|
||||
2. `manifest` 被修改
|
||||
3. 缓存更新
|
||||
|
||||
#### 视频
|
||||
|
||||
在 HTML5 发布之前,没有统一的标准来显示网页上的视频。大多数视频都是通过 Flash 等不同的插件显示的。但 HTML5 规定了使用 video 元素在网页上显示视频的标准方式。
|
||||
|
||||
目前,video 元素支持三种视频格式,如表 2 所示。
|
||||
|
||||
[][9]
|
||||
|
||||
下面的例子展示了 video 元素的使用:
|
||||
|
||||
```
|
||||
<! DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<video src=" vdeo.ogg" width="320" height="240" controls="controls">
|
||||
|
||||
This browser does not support the video element.
|
||||
|
||||
</video>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
例子使用了 Ogg 文件,并且可以在 Firefox、Opera 和 Chrome 中使用。要使视频在 Safari 和未来版本的 Chrome 中工作,我们必须添加一个 MPEG4 和 WebM 文件。
|
||||
|
||||
`video` 元素允许多个 `source` 元素。`source` 元素可以链接到不同的视频文件。浏览器将使用第一个识别的格式,如下所示:
|
||||
|
||||
```
|
||||
<video width="320" height="240" controls="controls">
|
||||
<source src="vdeo.ogg" type="video/ogg" />
|
||||
<source src=" vdeo.mp4" type="video/mp4" />
|
||||
<source src=" vdeo.webm" type="video/webm" />
|
||||
This browser does not support the video element.
|
||||
</video>
|
||||
```
|
||||
|
||||
[][10]
|
||||
|
||||
*图6:Canvas 的输出*
|
||||
|
||||
#### 音频
|
||||
|
||||
对于音频,情况类似于视频。在 HTML5 发布之前,在网页上播放音频没有统一的标准。大多数音频也通过 Flash 等不同的插件播放。但 HTML5 规定了通过使用音频元素在网页上播放音频的标准方式。音频元素用于播放声音文件和音频流。
|
||||
|
||||
目前,HTML5 `audio` 元素支持三种音频格式,如表 3 所示。
|
||||
|
||||
[][11]
|
||||
|
||||
`audio` 元素的使用如下所示:
|
||||
|
||||
```
|
||||
<! DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<audio src=" song.ogg" controls="controls">
|
||||
|
||||
This browser does not support the audio element.
|
||||
|
||||
</video>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
此例使用 Ogg 文件,并且可以在 Firefox、Opera 和 Chrome 中使用。要在 Safari 和 Chrome 的未来版本中使 audio 工作,我们必须添加一个 MP3 和 Wav 文件。
|
||||
|
||||
`audio` 元素允许多个 `source` 元素,它可以链接到不同的音频文件。浏览器将使用第一个识别的格式,如下所示:
|
||||
|
||||
```
|
||||
<audio controls="controls">
|
||||
<source src="song.ogg" type="audio/ogg" />
|
||||
<source src="song.mp3" type="audio/mpeg" />
|
||||
|
||||
This browser does not support the audio element.
|
||||
|
||||
</audio>
|
||||
```
|
||||
|
||||
#### 画布(Canvas)
|
||||
|
||||
要在网页上创建图形,HTML5 使用 画布 API。我们可以用它绘制任何东西,并且它使用 JavaScript。它通过避免从网络下载图像而提高网站性能。使用画布,我们可以绘制形状和线条、弧线和文本、渐变和图案。此外,画布可以让我们操作图像中甚至视频中的像素。你可以将 `canvas` 元素添加到 HTML 页面,如下所示:
|
||||
|
||||
```
|
||||
<canvas id="myCanvas" width="200" height="100"></canvas>
|
||||
```
|
||||
|
||||
画布元素不具有绘制元素的功能。我们可以通过使用 JavaScript 来实现绘制。所有绘画应在 JavaScript 中。
|
||||
|
||||
```
|
||||
<script type="text/javascript">
|
||||
var c=document.getElementById("myCanvas");
|
||||
var cxt=c.getContext("2d");
|
||||
cxt.fillStyle="blue";
|
||||
cxt.storkeStyle = "red";
|
||||
cxt.fillRect(10,10,100,100);
|
||||
cxt.storkeRect(10,10,100,100);
|
||||
</script>
|
||||
```
|
||||
|
||||
以上脚本的输出如图 6 所示。
|
||||
|
||||
你可以绘制许多对象,如弧、圆、线/垂直梯度等。
|
||||
|
||||
### HTML5 工具
|
||||
|
||||
为了有效操作,所有熟练的或业余的 Web 开发人员/设计人员都应该使用 HTML5 工具,当需要设置工作流/网站或执行重复任务时,这些工具非常有帮助。它们提高了网页设计的可用性。
|
||||
|
||||
以下是一些帮助创建很棒的网站的必要工具。
|
||||
|
||||
- **HTML5 Maker:** 用来在 HTML、JavaScript 和 CSS 的帮助下与网站内容交互。非常容易使用。它还允许我们开发幻灯片、滑块、HTML5 动画等。
|
||||
- **Liveweave:** 用来测试代码。它减少了保存代码并将其加载到屏幕上所花费的时间。在编辑器中粘贴代码即可得到结果。它非常易于使用,并为一些代码提供自动完成功能,这使得开发和测试更快更容易。
|
||||
- **Font dragr:** 在浏览器中预览定制的 Web 字体。它会直接载入该字体,以便你可以知道看起来是否正确。也提供了拖放界面,允许你拖动字形、Web 开放字体和矢量图形来马上测试。
|
||||
- **HTML5 Please:** 可以让我们找到与 HTML5 相关的任何内容。如果你想知道如何使用任何一个功能,你可以在 HTML Please 中搜索。它提供了支持的浏览器和设备的有用资源的列表,语法,以及如何使用元素的一般建议等。
|
||||
- **Modernizr:** 这是一个开源工具,用于给访问者浏览器提供最佳体验。使用此工具,你可以检测访问者的浏览器是否支持 HTML5 功能,并加载相应的脚本。
|
||||
- **Adobe Edge Animate:** 这是必须处理交互式 HTML 动画的 HTML5 开发人员的有用工具。它用于数字出版、网络和广告领域。此工具允许用户创建无瑕疵的动画,可以跨多个设备运行。
|
||||
- **Video.js:** 这是一款基于 JavaScript 的 HTML5 视频播放器。如果要将视频添加到你的网站,你应该使用此工具。它使视频看起来不错,并且是网站的一部分。
|
||||
- **The W3 Validator:** W3 验证工具测试 HTML、XHTML、SMIL、MathML 等中的网站标记的有效性。要测试任何网站的标记有效性,你必须选择文档类型为 HTML5 并输入你网页的 URL。这样做之后,你的代码将被检查,并将提供所有错误和警告。
|
||||
- **HTML5 Reset:** 此工具允许开发人员在 HTML5 中重写旧网站的代码。你可以使用这些工具为你网站的访问者提供一个良好的网络体验。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Palak Shah
|
||||
|
||||
作者是高级软件工程师。她喜欢探索新技术,学习创新概念。她也喜欢哲学。你可以通过 palak311@gmail.com 联系她。
|
||||
|
||||
--------------------
|
||||
via: http://opensourceforu.com/2017/06/introduction-to-html5/
|
||||
|
||||
作者:[Palak Shah][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:http://opensourceforu.com/author/palak-shah/
|
||||
[1]:http://opensourceforu.com/2017/06/introduction-to-html5/#disqus_thread
|
||||
[2]:http://opensourceforu.com/author/palak-shah/
|
||||
[3]:http://opensourceforu.com/wp-content/uploads/2017/05/Figure-1-7.jpg
|
||||
[4]:http://opensourceforu.com/wp-content/uploads/2017/05/Figure-2-5.jpg
|
||||
[5]:http://opensourceforu.com/wp-content/uploads/2017/05/Figure-3-2.jpg
|
||||
[6]:http://opensourceforu.com/wp-content/uploads/2017/05/Figure-4-2.jpg
|
||||
[7]:http://opensourceforu.com/wp-content/uploads/2017/05/table-1.jpg
|
||||
[8]:http://opensourceforu.com/wp-content/uploads/2017/05/Figure5-1.jpg
|
||||
[9]:http://opensourceforu.com/wp-content/uploads/2017/05/table-2.jpg
|
||||
[10]:http://opensourceforu.com/wp-content/uploads/2017/05/Figure6-1.jpg
|
||||
[11]:http://opensourceforu.com/wp-content/uploads/2017/05/table-3.jpg
|
||||
@@ -0,0 +1,112 @@
|
||||
运行在树莓派和 Arduino 上的开源社交机器人套件
|
||||
============
|
||||
|
||||

|
||||
|
||||
*Thecorpora 的发布的 “Q.bo One” 机器人基于 RPi 3 和 Arduino,并提供立体相机、麦克风、扬声器,以及视觉和语言识别。*
|
||||
|
||||
2010 年,作为一个开源概念验证和用于探索 AI 在多传感器、交互式机器人的能力的研究项目,机器人开发商 Francisco Paz 及它在巴塞罗那的 Thecorpora 公司推出了首款 [Qbo][6] “Cue-be-oh” 机器人。在今年 2 月移动世界大会上的预览之后,Thecorpora 把它放到了 Indiegogo 上,与 Arrow 合作推出了第一个批量生产的社交机器人版本。
|
||||
|
||||
[][7]
|
||||
|
||||
*Q.bo One 的左侧*
|
||||
|
||||
[][8]
|
||||
|
||||
*Q.bo One 的顶部*
|
||||
|
||||
像原来一样,新的 Q.bo One 有一个带眼睛的球形头(双立体相机)、耳朵(3 个麦克风)和嘴(扬声器),并由 WiFi 和蓝牙控制。 Q.bo One 也同样采用开源 Linux 软件和开放规格硬件。然而,它不是使用基于 Intel Atom 的 Mini-ITX 板,而是在与 Arduino 兼容的主板相连的 Raspberry Pi 3 上运行 Raspbian。
|
||||
|
||||
[][9]
|
||||
|
||||
*Q.bo One 侧视图**
|
||||
|
||||
Q.bo One 于 7 月中旬在 Indiegogo 上架,起价为 369 美元(早期买家)或 399 美元,有包括内置的树莓派 3 和基于 Arduino 的 “Qboard” 控制器板。它还有售价 $499 的完整套装。目前,Indiegogo 上的众筹目标是 $100,000,现在大概达成了 15%,并且它 12 月出货。
|
||||
|
||||
更专业的机器人工程师和嵌入式开发人员可能会想要使用价值 $99 的只有树莓派和 Qboard PCB 和软件的版本,或者提供没有电路板的机器人套件的 $249 版本。使用此版本,你可以用自己的 Arduino 控制器替换 Qboard,并将树莓派 3 替换为另一个 Linux SBC。该公司列出了 Banana Pi、BeagleBone、Tinker Board 以及[即将退市的 Intel Edison][10],作为兼容替代品的示例。
|
||||
|
||||
[][11]
|
||||
|
||||
*Q.bo One 套件*
|
||||
|
||||
与 2010 年的 Qbo 不同,Q.bo One 除了球面头部之外无法移动,它可以在双重伺服系统的帮助下在底座上旋转,以便跟踪声音和动作。Robotis Dynamixel 舵机同样开源,树莓派基于 [TurtleBot 3][23] 机器人工具包,除了左右之外,还可以上下移动。
|
||||
|
||||
[][12]
|
||||
|
||||
*Q.bo One 细节*
|
||||
|
||||
[][13]
|
||||
|
||||
*Qboard 细节*
|
||||
|
||||
Q.bo One 类似于基于 Linux 的 [Jibo][24] “社交机器人”,它于 2014 年在 Indiegogo 众筹,最后达到 360 万美元。然而,Jibo 还没有出货,[最近的推迟][25]迫使它在今年的某个时候发布一个版本。
|
||||
|
||||

|
||||
|
||||
*Q.bo One*
|
||||
|
||||
我们大胆预测 Q.bo One 将会在 2017 年接近 12 月出货。核心技术和 AI 软件已被证明,而树莓派和 Arduino 技术也是如此。Qboard 主板已经由 Arrow 制造和认证。
|
||||
|
||||
开源设计表明,即使是移动版本也不会有问题。这使它更像是滚动的人形生物 [Pepper][14],这是一个来自 Softbank 和 Aldeberan 类似的人工智能对话机器人。
|
||||
|
||||
Q.bo One 自原始版以来添加了一些技巧,例如由 20 个 LED 组成的“嘴巴”, 它以不同的、可编程的方式在语音中模仿嘴唇开合。如果你想点击机器人获得关注,那么它的头上还有三个触摸传感器。但是,你其实只需要说话就行,而 Q.bo One 会像一个可卡犬一样转身并凝视着你。
|
||||
|
||||
接口和你在树莓派 3 上的一样,它在我们的 [2017 黑客电路板调查][15]中消灭了其他对手。为树莓派 3 的 WiFi 和蓝牙安装了天线。
|
||||
|
||||
[][16]
|
||||
|
||||
*Q.bo One 软件架构*
|
||||
|
||||
[][17]
|
||||
|
||||
*Q.bo One 及 Scratch 编程*
|
||||
|
||||
Qboard(也称为 Q.board)在 Atmel ATSAMD21 MCU 上运行 Arduino 代码,并有三个麦克风、扬声器、触摸传感器、Dynamixel 控制器和用于嘴巴的 LED 矩阵。其他功能包括 GPIO、I2C接口和可连接到台式机的 micro-USB 口。
|
||||
|
||||
Q.bo One 可以识别脸部和追踪移动,机器人甚至可以在镜子中识别自己。在云连接的帮助下,机器人可以识别并与其他 Q.bo One 机器人交谈。机器人可以在自然语言处理的帮助下回答问题,并通过文字转语音朗读。
|
||||
|
||||
可以使用 Scratch 编程,它是机器人的主要功能,可以教孩子关于机器人和编程。机器人也是为教育者和制造者设计的,可以作为老年人的伴侣。
|
||||
|
||||
基于 Raspbian 的软件使用 OpenCV 进行视觉处理,并可以使用各种语言(包括 C++)进行编程。该软件还提供了 IBM Bluemix、NodeRED 和 ROS 的钩子。大概你也可以整合 [Alexa][18] 或 [Google Assistant][19]语音代理,虽然 Thecorpora 没有提及这一点。
|
||||
|
||||
|
||||
**更多信息**
|
||||
|
||||
Q.bo One 在 7 月中旬在 Indiegogo 上架,起价为完整套件 $369 和完整组合 $499 。出货预计在 2017 年 12 月。更多信息请参见 [Q.bo One 的 Indiegogo 页面][20] 和[Thecorpora 网站][21]。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: http://linuxgizmos.com/open-source-social-robot-kit-runs-on-raspberry-pi-and-arduino/
|
||||
|
||||
作者:[Eric Brown][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:http://linuxgizmos.com/open-source-social-robot-kit-runs-on-raspberry-pi-and-arduino/
|
||||
[1]:http://twitter.com/share?url=http://linuxgizmos.com/open-source-social-robot-kit-runs-on-raspberry-pi-and-arduino/&text=Open+source+social+robot+kit+runs+on+Raspberry+Pi+and+Arduino+
|
||||
[2]:https://plus.google.com/share?url=http://linuxgizmos.com/open-source-social-robot-kit-runs-on-raspberry-pi-and-arduino/
|
||||
[3]:http://www.facebook.com/sharer.php?u=http://linuxgizmos.com/open-source-social-robot-kit-runs-on-raspberry-pi-and-arduino/
|
||||
[4]:http://www.linkedin.com/shareArticle?mini=true&url=http://linuxgizmos.com/open-source-social-robot-kit-runs-on-raspberry-pi-and-arduino/
|
||||
[5]:http://reddit.com/submit?url=http://linuxgizmos.com/open-source-social-robot-kit-runs-on-raspberry-pi-and-arduino/&title=Open%20source%20social%20robot%20kit%20runs%20on%20Raspberry%20Pi%20and%20Arduino
|
||||
[6]:http://linuxdevices.linuxgizmos.com/open-source-robot-is-all-eyes/
|
||||
[7]:http://linuxgizmos.com/files/thecorpora_qboone.jpg
|
||||
[8]:http://linuxgizmos.com/files/thecorpora_qboone2.jpg
|
||||
[9]:http://linuxgizmos.com/files/thecorpora_qboone_side.jpg
|
||||
[10]:http://linuxgizmos.com/intel-pulls-the-plug-on-its-joule-edison-and-galileo-boards/
|
||||
[11]:http://linuxgizmos.com/files/thecorpora_qboone_kit.jpg
|
||||
[12]:http://linuxgizmos.com/files/thecorpora_qboone_detail.jpg
|
||||
[13]:http://linuxgizmos.com/files/thecorpora_qboone_qboard.jpg
|
||||
[14]:http://linuxgizmos.com/worlds-first-emotional-robot-runs-linux/
|
||||
[15]:http://linuxgizmos.com/2017-hacker-board-survey-raspberry-pi-still-rules-but-x86-sbcs-make-gains/
|
||||
[16]:http://linuxgizmos.com/files/thecorpora_qboone_arch.jpg
|
||||
[17]:http://linuxgizmos.com/files/thecorpora_qboone_scratch.jpg
|
||||
[18]:http://linuxgizmos.com/how-to-add-alexa-to-your-raspberry-pi-3-gizmo/
|
||||
[19]:http://linuxgizmos.com/free-raspberry-pi-voice-kit-taps-google-assistant-sdk/
|
||||
[20]:https://www.indiegogo.com/projects/q-bo-one-an-open-source-robot-for-everyone#/
|
||||
[21]:http://thecorpora.com/
|
||||
[22]:http://linuxgizmos.com/open-source-social-robot-kit-runs-on-raspberry-pi-and-arduino/
|
||||
[23]:http://linuxgizmos.com/ubuntu-driven-turtlebot-gets-a-major-rev-with-a-pi-or-joule-in-the-drivers-seat/
|
||||
[24]:http://linuxgizmos.com/cheery-social-robot-owes-it-all-to-its-inner-linux/
|
||||
[25]:https://www.slashgear.com/jibo-delayed-to-2017-as-social-robot-hits-more-hurdles-20464725/
|
||||
@@ -0,0 +1,179 @@
|
||||
开发一个 Linux 调试器(八):堆栈展开
|
||||
============================================================
|
||||
|
||||
有时你需要知道的最重要的信息是什么,你当前的程序状态是如何到达那里的。这有一个 `backtrace` 命令,它给你提供了程序当前的函数调用链。这篇文章将向你展示如何在 x86_64 上实现堆栈展开以生成这样的回溯。
|
||||
|
||||
### 系列索引
|
||||
|
||||
这些链接将会随着其他帖子的发布而上线。
|
||||
|
||||
1. [准备环境][1]
|
||||
2. [断点][2]
|
||||
3. [寄存器和内存][3]
|
||||
4. [ELF 和 DWARF][4]
|
||||
5. [源码和信号][5]
|
||||
6. [源码级逐步执行][6]
|
||||
7. [源码级断点][7]
|
||||
8. [堆栈展开][8]
|
||||
9. 读取变量
|
||||
10. 之后步骤
|
||||
|
||||
用下面的程序作为例子:
|
||||
|
||||
```
|
||||
void a() {
|
||||
//stopped here
|
||||
}
|
||||
|
||||
void b() {
|
||||
a();
|
||||
}
|
||||
|
||||
void c() {
|
||||
a();
|
||||
}
|
||||
|
||||
int main() {
|
||||
b();
|
||||
c();
|
||||
}
|
||||
```
|
||||
|
||||
如果调试器停在 `//stopped here' 这行,那么有两种方法可以达到:`main->b->a` 或 `main->c->a`。如果我们用 LLDB 设置一个断点,继续执行并请求一个回溯,那么我们将得到以下内容:
|
||||
|
||||
```
|
||||
* frame #0: 0x00000000004004da a.out`a() + 4 at bt.cpp:3
|
||||
frame #1: 0x00000000004004e6 a.out`b() + 9 at bt.cpp:6
|
||||
frame #2: 0x00000000004004fe a.out`main + 9 at bt.cpp:14
|
||||
frame #3: 0x00007ffff7a2e830 libc.so.6`__libc_start_main + 240 at libc-start.c:291
|
||||
frame #4: 0x0000000000400409 a.out`_start + 41
|
||||
```
|
||||
|
||||
这说明我们目前在函数 `a` 中,`a` 从函数 `b` 中跳转,`b` 从 `main` 中跳转等等。最后两个帧是编译器如何引导 `main` 函数的。
|
||||
|
||||
现在的问题是我们如何在 x86_64 上实现。最稳健的方法是解析 ELF 文件的 `.eh_frame` 部分,并解决如何从那里展开堆栈,但这会很痛苦。你可以使用 `libunwind` 或类似的来做,但这很无聊。相反,我们假设编译器以某种方式设置了堆栈,我们将手动遍历它。为了做到这一点,我们首先需要了解堆栈的布局。
|
||||
|
||||
```
|
||||
High
|
||||
| ... |
|
||||
+---------+
|
||||
+24| Arg 1 |
|
||||
+---------+
|
||||
+16| Arg 2 |
|
||||
+---------+
|
||||
+ 8| Return |
|
||||
+---------+
|
||||
EBP+--> |Saved EBP|
|
||||
+---------+
|
||||
- 8| Var 1 |
|
||||
+---------+
|
||||
ESP+--> | Var 2 |
|
||||
+---------+
|
||||
| ... |
|
||||
Low
|
||||
|
||||
```
|
||||
|
||||
如你所见,最后一个堆栈帧的帧指针存储在当前堆栈帧的开始处,创建一个链接的指针列表。堆栈依据这个链表解开。我们可以通过查找 DWARF 信息中的返回地址来找出列表中下一帧的函数。一些编译器将忽略跟踪 `EBP` 的帧基址,因为这可以表示为 `ESP` 的偏移量,并可以释放一个额外的寄存器。即使启用了优化,传递 `-fno-omit-frame-pointer` 到 GCC 或 Clang 会强制它遵循我们依赖的约定。
|
||||
|
||||
我们将在 `print_backtrace` 函数中完成所有的工作:
|
||||
|
||||
```
|
||||
void debugger::print_backtrace() {
|
||||
```
|
||||
|
||||
首先要决定的是使用什么格式打印出帧信息。我用了一个 lambda 来推出这个方法:
|
||||
|
||||
```
|
||||
auto output_frame = [frame_number = 0] (auto&& func) mutable {
|
||||
std::cout << "frame #" << frame_number++ << ": 0x" << dwarf::at_low_pc(func)
|
||||
<< ' ' << dwarf::at_name(func) << std::endl;
|
||||
};
|
||||
```
|
||||
|
||||
打印输出的第一帧是当前正在执行的帧。我们可以通过查找 DWARF 中的当前程序计数器来获取此帧的信息:
|
||||
|
||||
```
|
||||
auto current_func = get_function_from_pc(get_pc());
|
||||
output_frame(current_func);
|
||||
```
|
||||
|
||||
接下来我们需要获取当前函数的帧指针和返回地址。帧指针存储在 `rbp` 寄存器中,返回地址是从帧指针堆栈起的 8 字节。
|
||||
|
||||
```
|
||||
auto frame_pointer = get_register_value(m_pid, reg::rbp);
|
||||
auto return_address = read_memory(frame_pointer+8);
|
||||
```
|
||||
|
||||
现在我们拥有了展开堆栈所需的所有信息。我只需要继续展开,直到调试器命中 `main`,但是当帧指针为 `0x0` 时,你也可以选择停止,这些是你在调用 `main` 函数之前调用的函数。我们将从每帧抓取帧指针和返回地址,并打印出信息。
|
||||
|
||||
```
|
||||
while (dwarf::at_name(current_func) != "main") {
|
||||
current_func = get_function_from_pc(return_address);
|
||||
output_frame(current_func);
|
||||
frame_pointer = read_memory(frame_pointer);
|
||||
return_address = read_memory(frame_pointer+8);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
就是这样!以下是整个函数:
|
||||
|
||||
```
|
||||
void debugger::print_backtrace() {
|
||||
auto output_frame = [frame_number = 0] (auto&& func) mutable {
|
||||
std::cout << "frame #" << frame_number++ << ": 0x" << dwarf::at_low_pc(func)
|
||||
<< ' ' << dwarf::at_name(func) << std::endl;
|
||||
};
|
||||
|
||||
auto current_func = get_function_from_pc(get_pc());
|
||||
output_frame(current_func);
|
||||
|
||||
auto frame_pointer = get_register_value(m_pid, reg::rbp);
|
||||
auto return_address = read_memory(frame_pointer+8);
|
||||
|
||||
while (dwarf::at_name(current_func) != "main") {
|
||||
current_func = get_function_from_pc(return_address);
|
||||
output_frame(current_func);
|
||||
frame_pointer = read_memory(frame_pointer);
|
||||
return_address = read_memory(frame_pointer+8);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 添加命令
|
||||
|
||||
当然,我们必须向用户公开这个命令。
|
||||
|
||||
```
|
||||
else if(is_prefix(command, "backtrace")) {
|
||||
print_backtrace();
|
||||
}
|
||||
```
|
||||
|
||||
### 测试
|
||||
|
||||
测试此功能的一个方法是通过编写一个测试程序与一堆互相调用的小函数。设置几个断点,跳到代码附近,并确保你的回溯是准确的。
|
||||
|
||||
我们已经从一个只能产生并附加到其他程序的程序走了很长的路。本系列的倒数第二篇文章将通过支持读写变量来完成调试器的实现。在此之前,你可以在[这里][9]找到这个帖子的代码。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://blog.tartanllama.xyz/c++/2017/06/24/writing-a-linux-debugger-unwinding/
|
||||
|
||||
作者:[Simon Brand][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://twitter.com/TartanLlama
|
||||
[1]:https://linux.cn/article-8626-1.html
|
||||
[2]:https://linux.cn/article-8645-1.html
|
||||
[3]:https://linux.cn/article-8663-1.html
|
||||
[4]:https://linux.cn/article-8719-1.html
|
||||
[5]:https://linux.cn/article-8812-1.html
|
||||
[6]:https://linux.cn/article-8813-1.html
|
||||
[7]:https://linux.cn/article-8890-1.html
|
||||
[8]:https://blog.tartanllama.xyz/c++/2017/06/24/writing-a-linux-debugger-unwinding/
|
||||
[9]:https://github.com/TartanLlama/minidbg/tree/tut_unwind
|
||||
@@ -0,0 +1,716 @@
|
||||
JavaScript 函数式编程介绍
|
||||
============================================================
|
||||
|
||||
> 探索函数式编程,通过它让你的程序更具有可读性和易于调试
|
||||
|
||||
|
||||

|
||||
|
||||
>Image credits : Steve Jurvetson via [Flickr][80] (CC-BY-2.0)
|
||||
|
||||
当 Brendan Eich 在 1995 年创造 JavaScript 时,他原本打算[将 Scheme 移植到浏览器里][81] 。Scheme 作为 Lisp 的方言,是一种函数式编程语言。而当 Eich 被告知新的语言应该是一种可以与 Java 相比的脚本语言后,他最终确立了一种拥有 C 风格语法的语言(也和 Java 一样),但将函数视作一等公民。而 Java 直到版本 8 才从技术上将函数视为一等公民,虽然你可以用匿名类来模拟它。这个特性允许 JavaScript 通过函数式范式编程。
|
||||
|
||||
JavaScript 是一个多范式语言,允许你自由地混合和使用面向对象式、过程式和函数式的编程范式。最近,函数式编程越来越火热。在诸如 [Angular][82] 和 [React][83] 这样的框架中,通过使用不可变数据结构可以切实提高性能。不可变是函数式编程的核心原则,它以及纯函数使得编写和调试程序变得更加容易。使用函数来代替程序的循环可以提高程序的可读性并使它更加优雅。总之,函数式编程拥有很多优点。
|
||||
|
||||
### 什么不是函数式编程
|
||||
|
||||
在讨论什么是函数式编程前,让我们先排除那些不属于函数式编程的东西。实际上它们是你需要丢弃的语言组件(再见,老朋友):
|
||||
|
||||
* 循环:
|
||||
* `while`
|
||||
* `do...while`
|
||||
* `for`
|
||||
* `for...of`
|
||||
* `for...in`
|
||||
* 用 `var` 或者 `let` 来声明变量
|
||||
* 没有返回值的函数
|
||||
* 改变对象的属性 (比如: `o.x = 5;`)
|
||||
* 改变数组本身的方法:
|
||||
* `copyWithin`
|
||||
* `fill`
|
||||
* `pop`
|
||||
* `push`
|
||||
* `reverse`
|
||||
* `shift`
|
||||
* `sort`
|
||||
* `splice`
|
||||
* `unshift`
|
||||
* 改变映射本身的方法:
|
||||
* `clear`
|
||||
* `delete`
|
||||
* `set`
|
||||
* 改变集合本身的方法:
|
||||
* `add`
|
||||
* `clear`
|
||||
* `delete`
|
||||
|
||||
脱离这些特性应该如何编写程序呢?这是我们将在后面探索的问题。
|
||||
|
||||
### 纯函数
|
||||
|
||||
你的程序中包含函数不一定意味着你正在进行函数式编程。函数式范式将<ruby>纯函数<rt>pure function</rt></ruby>和<ruby>非纯函数<rt>impure function</rt></ruby>区分开。鼓励你编写纯函数。纯函数必须满足下面的两个属性:
|
||||
|
||||
* 引用透明:函数在传入相同的参数后永远返回相同的返回值。这意味着该函数不依赖于任何可变状态。
|
||||
* 无副作用:函数不能导致任何副作用。副作用可能包括 I/O(比如向终端或者日志文件写入),改变一个不可变的对象,对变量重新赋值等等。
|
||||
|
||||
我们来看一些例子。首先,`multiply` 就是一个纯函数的例子,它在传入相同的参数后永远返回相同的返回值,并且不会导致副作用。
|
||||
|
||||
```
|
||||
function multiply(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
```
|
||||
|
||||
下面是非纯函数的例子。`canRide` 函数依赖捕获的 `heightRequirement` 变量。被捕获的变量不一定导致一个函数是非纯函数,除非它是一个可变的变量(或者可以被重新赋值)。这种情况下使用 `let` 来声明这个变量,意味着可以对它重新赋值。`multiply` 函数是非纯函数,因为它会导致在 console 上输出。
|
||||
|
||||
```
|
||||
let heightRequirement = 46;
|
||||
|
||||
// Impure because it relies on a mutable (reassignable) variable.
|
||||
function canRide(height) {
|
||||
return height >= heightRequirement;
|
||||
}
|
||||
|
||||
// Impure because it causes a side-effect by logging to the console.
|
||||
function multiply(a, b) {
|
||||
console.log('Arguments: ', a, b);
|
||||
return a * b;
|
||||
}
|
||||
```
|
||||
|
||||
下面的列表包含着 JavaScript 内置的非纯函数。你可以指出它们不满足两个属性中的哪个吗?
|
||||
|
||||
* `console.log`
|
||||
* `element.addEventListener`
|
||||
* `Math.random`
|
||||
* `Date.now`
|
||||
* `$.ajax` (这里 `$` 代表你使用的 Ajax 库)
|
||||
|
||||
理想的程序中所有的函数都是纯函数,但是从上面的函数列表可以看出,任何有意义的程序都将包含非纯函数。大多时候我们需要进行 AJAX 调用,检查当前日期或者获取一个随机数。一个好的经验法则是遵循 80/20 规则:函数中有 80% 应该是纯函数,剩下的 20% 的必要性将不可避免地是非纯函数。
|
||||
|
||||
使用纯函数有几个优点:
|
||||
|
||||
* 它们很容易导出和调试,因为它们不依赖于可变的状态。
|
||||
* 返回值可以被缓存或者“记忆”来避免以后重复计算。
|
||||
* 它们很容易测试,因为没有需要模拟(mock)的依赖(比如日志,AJAX,数据库等等)。
|
||||
|
||||
你编写或者使用的函数返回空(换句话说它没有返回值),那代表它是非纯函数。
|
||||
|
||||
### 不变性
|
||||
|
||||
让我们回到捕获变量的概念上。来看看 `canRide` 函数。我们认为它是一个非纯函数,因为 `heightRequirement` 变量可以被重新赋值。下面是一个构造出来的例子来说明如何用不可预测的值来对它重新赋值。
|
||||
|
||||
```
|
||||
let heightRequirement = 46;
|
||||
|
||||
function canRide(height) {
|
||||
return height >= heightRequirement;
|
||||
}
|
||||
|
||||
// Every half second, set heightRequirement to a random number between 0 and 200.
|
||||
setInterval(() => heightRequirement = Math.floor(Math.random() * 201), 500);
|
||||
|
||||
const mySonsHeight = 47;
|
||||
|
||||
// Every half second, check if my son can ride.
|
||||
// Sometimes it will be true and sometimes it will be false.
|
||||
setInterval(() => console.log(canRide(mySonsHeight)), 500);
|
||||
```
|
||||
|
||||
我要再次强调被捕获的变量不一定会使函数成为非纯函数。我们可以通过只是简单地改变 `heightRequirement` 的声明方式来使 `canRide` 函数成为纯函数。
|
||||
|
||||
```
|
||||
const heightRequirement = 46;
|
||||
|
||||
function canRide(height) {
|
||||
return height >= heightRequirement;
|
||||
}
|
||||
```
|
||||
|
||||
通过用 `const` 来声明变量意味着它不能被再次赋值。如果尝试对它重新赋值,运行时引擎将抛出错误;那么,如果用对象来代替数字来存储所有的“常量”怎么样?
|
||||
|
||||
```
|
||||
const constants = {
|
||||
heightRequirement: 46,
|
||||
// ... other constants go here
|
||||
};
|
||||
|
||||
function canRide(height) {
|
||||
return height >= constants.heightRequirement;
|
||||
}
|
||||
```
|
||||
|
||||
我们用了 `const` ,所以这个变量不能被重新赋值,但是还有一个问题:这个对象可以被改变。下面的代码展示了,为了真正使其不可变,你不仅需要防止它被重新赋值,你也需要不可变的数据结构。JavaScript 语言提供了 `Object.freeze` 方法来阻止对象被改变。
|
||||
|
||||
```
|
||||
'use strict';
|
||||
|
||||
// CASE 1: 对象的属性是可变的,并且变量可以被再次赋值。
|
||||
let o1 = { foo: 'bar' };
|
||||
|
||||
// 改变对象的属性
|
||||
o1.foo = 'something different';
|
||||
|
||||
// 对变量再次赋值
|
||||
o1 = { message: "I'm a completely new object" };
|
||||
|
||||
|
||||
// CASE 2: 对象的属性还是可变的,但是变量不能被再次赋值。
|
||||
const o2 = { foo: 'baz' };
|
||||
|
||||
// 仍然能改变对象
|
||||
o2.foo = 'Something different, yet again';
|
||||
|
||||
// 不能对变量再次赋值
|
||||
// o2 = { message: 'I will cause an error if you uncomment me' }; // Error!
|
||||
|
||||
|
||||
// CASE 3: 对象的属性是不可变的,但是变量可以被再次赋值。
|
||||
let o3 = Object.freeze({ foo: "Can't mutate me" });
|
||||
|
||||
// 不能改变对象的属性
|
||||
// o3.foo = 'Come on, uncomment me. I dare ya!'; // Error!
|
||||
|
||||
// 还是可以对变量再次赋值
|
||||
o3 = { message: "I'm some other object, and I'm even mutable -- so take that!" };
|
||||
|
||||
|
||||
// CASE 4: 对象的属性是不可变的,并且变量不能被再次赋值。这是我们想要的!!!!!!!!
|
||||
const o4 = Object.freeze({ foo: 'never going to change me' });
|
||||
|
||||
// 不能改变对象的属性
|
||||
// o4.foo = 'talk to the hand' // Error!
|
||||
|
||||
// 不能对变量再次赋值
|
||||
// o4 = { message: "ain't gonna happen, sorry" }; // Error
|
||||
```
|
||||
|
||||
|
||||
不变性适用于所有的数据结构,包括数组、映射和集合。它意味着不能调用例如 `Array.prototype.push` 等会导致本身改变的方法,因为它会改变已经存在的数组。可以通过创建一个含有原来元素和新加元素的新数组,而不是将新元素加入一个已经存在的数组。其实所有会导致数组本身被修改的方法都可以通过一个返回修改好的新数组的函数代替。
|
||||
|
||||
```
|
||||
'use strict';
|
||||
|
||||
const a = Object.freeze([4, 5, 6]);
|
||||
|
||||
// Instead of: a.push(7, 8, 9);
|
||||
const b = a.concat(7, 8, 9);
|
||||
|
||||
// Instead of: a.pop();
|
||||
const c = a.slice(0, -1);
|
||||
|
||||
// Instead of: a.unshift(1, 2, 3);
|
||||
const d = [1, 2, 3].concat(a);
|
||||
|
||||
// Instead of: a.shift();
|
||||
const e = a.slice(1);
|
||||
|
||||
// Instead of: a.sort(myCompareFunction);
|
||||
const f = R.sort(myCompareFunction, a); // R = Ramda
|
||||
|
||||
// Instead of: a.reverse();
|
||||
const g = R.reverse(a); // R = Ramda
|
||||
|
||||
// 留给读者的练习:
|
||||
// copyWithin
|
||||
// fill
|
||||
// splice
|
||||
```
|
||||
|
||||
[映射][84] 和 [集合][85] 也很相似。可以通过返回一个新的修改好的映射或者集合来代替使用会修改其本身的函数。
|
||||
|
||||
```
|
||||
const map = new Map([
|
||||
[1, 'one'],
|
||||
[2, 'two'],
|
||||
[3, 'three']
|
||||
]);
|
||||
|
||||
// Instead of: map.set(4, 'four');
|
||||
const map2 = new Map([...map, [4, 'four']]);
|
||||
|
||||
// Instead of: map.delete(1);
|
||||
const map3 = new Map([...map].filter(([key]) => key !== 1));
|
||||
|
||||
// Instead of: map.clear();
|
||||
const map4 = new Map();
|
||||
```
|
||||
|
||||
```
|
||||
const set = new Set(['A', 'B', 'C']);
|
||||
|
||||
// Instead of: set.add('D');
|
||||
const set2 = new Set([...set, 'D']);
|
||||
|
||||
// Instead of: set.delete('B');
|
||||
const set3 = new Set([...set].filter(key => key !== 'B'));
|
||||
|
||||
// Instead of: set.clear();
|
||||
const set4 = new Set();
|
||||
```
|
||||
|
||||
我想提一句如果你在使用 TypeScript(我非常喜欢 TypeScript),你可以用 `Readonly<T>`、`ReadonlyArray<T>`、`ReadonlyMap<K, V>` 和 `ReadonlySet<T>` 接口来在编译期检查你是否尝试更改这些对象,有则抛出编译错误。如果在对一个对象字面量或者数组调用 `Object.freeze`,编译器会自动推断它是只读的。由于映射和集合在其内部表达,所以在这些数据结构上调用 `Object.freeze` 不起作用。但是你可以轻松地告诉编译器它们是只读的变量。
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
*TypeScript 只读接口*
|
||||
|
||||
好,所以我们可以通过创建新的对象来代替修改原来的对象,但是这样不会导致性能损失吗?当然会。确保在你自己的应用中做了性能测试。如果你需要提高性能,可以考虑使用 [Immutable.js][86]。Immutable.js 用[持久的数据结构][91] 实现了[链表][87]、[堆栈][88]、[映射][89]、[集合][90]和其他数据结构。使用了如同 Clojure 和 Scala 这样的函数式语言中相同的技术。
|
||||
|
||||
```
|
||||
// Use in place of `[]`.
|
||||
const list1 = Immutable.List(['A', 'B', 'C']);
|
||||
const list2 = list1.push('D', 'E');
|
||||
|
||||
console.log([...list1]); // ['A', 'B', 'C']
|
||||
console.log([...list2]); // ['A', 'B', 'C', 'D', 'E']
|
||||
|
||||
|
||||
// Use in place of `new Map()`
|
||||
const map1 = Immutable.Map([
|
||||
['one', 1],
|
||||
['two', 2],
|
||||
['three', 3]
|
||||
]);
|
||||
const map2 = map1.set('four', 4);
|
||||
|
||||
console.log([...map1]); // [['one', 1], ['two', 2], ['three', 3]]
|
||||
console.log([...map2]); // [['one', 1], ['two', 2], ['three', 3], ['four', 4]]
|
||||
|
||||
|
||||
// Use in place of `new Set()`
|
||||
const set1 = Immutable.Set([1, 2, 3, 3, 3, 3, 3, 4]);
|
||||
const set2 = set1.add(5);
|
||||
|
||||
console.log([...set1]); // [1, 2, 3, 4]
|
||||
console.log([...set2]); // [1, 2, 3, 4, 5]
|
||||
```
|
||||
|
||||
### 函数组合
|
||||
|
||||
记不记得在中学时我们学过一些像 `(f ∘ g)(x)` 的东西?你那时可能想,“我什么时候会用到这些?”,好了,现在就用到了。你准备好了吗?`f ∘ g`读作 “函数 f 和函数 g 组合”。对它的理解有两种等价的方式,如等式所示: `(f ∘ g)(x) = f(g(x))`。你可以认为 `f ∘ g` 是一个单独的函数,或者视作将调用函数 `g` 的结果作为参数传给函数 `f`。注意这些函数是从右向左依次调用的,先执行 `g`,接下来执行 `f`。
|
||||
|
||||
关于函数组合的几个要点:
|
||||
|
||||
1. 我们可以组合任意数量的函数(不仅限于 2 个)。
|
||||
2. 组合函数的一个方式是简单地把一个函数的输出作为下一个函数的输入(比如 `f(g(x))`)。
|
||||
|
||||
```
|
||||
// h(x) = x + 1
|
||||
// number -> number
|
||||
function h(x) {
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
// g(x) = x^2
|
||||
// number -> number
|
||||
function g(x) {
|
||||
return x * x;
|
||||
}
|
||||
|
||||
// f(x) = convert x to string
|
||||
// number -> string
|
||||
function f(x) {
|
||||
return x.toString();
|
||||
}
|
||||
|
||||
// y = (f ∘ g ∘ h)(1)
|
||||
const y = f(g(h(1)));
|
||||
console.log(y); // '4'
|
||||
```
|
||||
|
||||
[Ramda][92] 和 [lodash][93] 之类的库提供了更优雅的方式来组合函数。我们可以在更多的在数学意义上处理函数组合,而不是简单地将一个函数的返回值传递给下一个函数。我们可以创建一个由这些函数组成的单一复合函数(就是 `(f ∘ g)(x)`)。
|
||||
|
||||
```
|
||||
// h(x) = x + 1
|
||||
// number -> number
|
||||
function h(x) {
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
// g(x) = x^2
|
||||
// number -> number
|
||||
function g(x) {
|
||||
return x * x;
|
||||
}
|
||||
|
||||
// f(x) = convert x to string
|
||||
// number -> string
|
||||
function f(x) {
|
||||
return x.toString();
|
||||
}
|
||||
|
||||
// R = Ramda
|
||||
// composite = (f ∘ g ∘ h)
|
||||
const composite = R.compose(f, g, h);
|
||||
|
||||
// Execute single function to get the result.
|
||||
const y = composite(1);
|
||||
console.log(y); // '4'
|
||||
```
|
||||
|
||||
好了,我们可以在 JavaScript 中组合函数了。接下来呢?好,如果你已经入门了函数式编程,理想中你的程序将只有函数的组合。代码里没有循环(`for`, `for...of`, `for...in`, `while`, `do`),基本没有。你可能觉得那是不可能的。并不是这样。我们下面的两个话题是:递归和高阶函数。
|
||||
|
||||
### 递归
|
||||
|
||||
假设你想实现一个计算数字的阶乘的函数。 让我们回顾一下数学中阶乘的定义:
|
||||
|
||||
`n! = n * (n-1) * (n-2) * ... * 1`.
|
||||
|
||||
`n!` 是从 `n` 到 `1` 的所有整数的乘积。我们可以编写一个循环轻松地计算出结果。
|
||||
|
||||
```
|
||||
function iterativeFactorial(n) {
|
||||
let product = 1;
|
||||
for (let i = 1; i <= n; i++) {
|
||||
product *= i;
|
||||
}
|
||||
return product;
|
||||
}
|
||||
```
|
||||
|
||||
注意 `product` 和 `i` 都在循环中被反复重新赋值。这是解决这个问题的标准过程式方法。如何用函数式的方法解决这个问题呢?我们需要消除循环,确保没有变量被重新赋值。递归是函数式程序员的最有力的工具之一。递归需要我们将整体问题分解为类似整体问题的子问题。
|
||||
|
||||
计算阶乘是一个很好的例子,为了计算 `n!` 我们需要将 n 乘以所有比它小的正整数。它的意思就相当于:
|
||||
|
||||
`n! = n * (n-1)!`
|
||||
|
||||
啊哈!我们发现了一个解决 `(n-1)!` 的子问题,它类似于整个问题 `n!`。还有一个需要注意的地方就是基础条件。基础条件告诉我们何时停止递归。 如果我们没有基础条件,那么递归将永远持续。 实际上,如果有太多的递归调用,程序会抛出一个堆栈溢出错误。啊哈!
|
||||
|
||||
```
|
||||
function recursiveFactorial(n) {
|
||||
// Base case -- stop the recursion
|
||||
if (n === 0) {
|
||||
return 1; // 0! is defined to be 1.
|
||||
}
|
||||
return n * recursiveFactorial(n - 1);
|
||||
}
|
||||
```
|
||||
|
||||
然后我们来计算 `recursiveFactorial(20000)` 因为……,为什么不呢?当我们这样做的时候,我们得到了这个结果:
|
||||
|
||||

|
||||
|
||||
*堆栈溢出错误*
|
||||
|
||||
这里发生了什么?我们得到一个堆栈溢出错误!这不是无穷的递归导致的。我们已经处理了基础条件(`n === 0` 的情况)。那是因为浏览器的堆栈大小是有限的,而我们的代码使用了越过了这个大小的堆栈。每次对 `recursiveFactorial` 的调用导致了新的帧被压入堆栈中,就像一个盒子压在另一个盒子上。每当 `recursiveFactorial` 被调用,一个新的盒子被放在最上面。下图展示了在计算 `recursiveFactorial(3)` 时堆栈的样子。注意在真实的堆栈中,堆栈顶部的帧将存储在执行完成后应该返回的内存地址,但是我选择用变量 `r` 来表示返回值,因为 JavaScript 开发者一般不需要考虑内存地址。
|
||||
|
||||
")
|
||||
|
||||
*递归计算 3! 的堆栈(三次乘法)*
|
||||
|
||||
你可能会想象当计算 `n = 20000` 时堆栈会更高。我们可以做些什么优化它吗?当然可以。作为 ES2015 (又名 ES6) 标准的一部分,有一个优化用来解决这个问题。它被称作<ruby>尾调用优化<rt>proper tail calls optimization</rt></ruby>(PTC)。当递归函数做的最后一件事是调用自己并返回结果的时候,它使得浏览器删除或者忽略堆栈帧。实际上,这个优化对于相互递归函数也是有效的,但是为了简单起见,我们还是来看单一递归函数。
|
||||
|
||||
你可能会注意到,在递归函数调用之后,还要进行一次额外的计算(`n * r`)。那意味着浏览器不能通过 PTC 来优化递归;然而,我们可以通过重写函数使最后一步变成递归调用以便优化。一个窍门是将中间结果(在这里是 `product`)作为参数传递给函数。
|
||||
|
||||
```
|
||||
'use strict';
|
||||
|
||||
// Optimized for tail call optimization.
|
||||
function factorial(n, product = 1) {
|
||||
if (n === 0) {
|
||||
return product;
|
||||
}
|
||||
return factorial(n - 1, product * n)
|
||||
}
|
||||
```
|
||||
|
||||
让我们来看看优化后的计算 `factorial(3)` 时的堆栈。如下图所示,堆栈不会增长到超过两层。原因是我们把必要的信息都传到了递归函数中(比如 `product`)。所以,在 `product` 被更新后,浏览器可以丢弃掉堆栈中原先的帧。你可以在图中看到每次最上面的帧下沉变成了底部的帧,原先底部的帧被丢弃,因为不再需要它了。
|
||||
|
||||
 using PTC")
|
||||
|
||||
*递归计算 3! 的堆栈(三次乘法)使用 PTC*
|
||||
|
||||
现在选一个浏览器运行吧,假设你在使用 Safari,你会得到 `Infinity`(它是比在 JavaScript 中能表达的最大值更大的数)。但是我们没有得到堆栈溢出错误,那很不错!现在在其他的浏览器中呢怎么样呢?Safari 可能现在乃至将来是实现 PTC 的唯一一个浏览器。看看下面的兼容性表格:
|
||||
|
||||

|
||||
|
||||
*PTC 兼容性*
|
||||
|
||||
其他浏览器提出了一种被称作<ruby>[语法级尾调用][95]<rt>syntactic tail calls</rt></ruby>(STC)的竞争标准。“语法级”意味着你需要用新的语法来标识你想要执行尾递归优化的函数。即使浏览器还没有广泛支持,但是把你的递归函数写成支持尾递归优化的样子还是一个好主意。
|
||||
|
||||
### 高阶函数
|
||||
|
||||
我们已经知道 JavaScript 将函数视作一等公民,可以把函数像其他值一样传递。所以,把一个函数传给另一个函数也很常见。我们也可以让函数返回一个函数。就是它!我们有高阶函数。你可能已经很熟悉几个在 `Array.prototype` 中的高阶函数。比如 `filter`、`map` 和 `reduce` 就在其中。对高阶函数的一种理解是:它是接受(一般会调用)一个回调函数参数的函数。让我们来看看一些内置的高阶函数的例子:
|
||||
|
||||
```
|
||||
const vehicles = [
|
||||
{ make: 'Honda', model: 'CR-V', type: 'suv', price: 24045 },
|
||||
{ make: 'Honda', model: 'Accord', type: 'sedan', price: 22455 },
|
||||
{ make: 'Mazda', model: 'Mazda 6', type: 'sedan', price: 24195 },
|
||||
{ make: 'Mazda', model: 'CX-9', type: 'suv', price: 31520 },
|
||||
{ make: 'Toyota', model: '4Runner', type: 'suv', price: 34210 },
|
||||
{ make: 'Toyota', model: 'Sequoia', type: 'suv', price: 45560 },
|
||||
{ make: 'Toyota', model: 'Tacoma', type: 'truck', price: 24320 },
|
||||
{ make: 'Ford', model: 'F-150', type: 'truck', price: 27110 },
|
||||
{ make: 'Ford', model: 'Fusion', type: 'sedan', price: 22120 },
|
||||
{ make: 'Ford', model: 'Explorer', type: 'suv', price: 31660 }
|
||||
];
|
||||
|
||||
const averageSUVPrice = vehicles
|
||||
.filter(v => v.type === 'suv')
|
||||
.map(v => v.price)
|
||||
.reduce((sum, price, i, array) => sum + price / array.length, 0);
|
||||
|
||||
console.log(averageSUVPrice); // 33399
|
||||
```
|
||||
|
||||
注意我们在一个数组对象上调用其方法,这是面向对象编程的特性。如果我们想要更函数式一些,我们可以用 Rmmda 或者 lodash/fp 提供的函数。注意如果我们使用 `R.compose` 的话,需要倒转函数的顺序,因为它从右向左依次调用函数(从底向上);然而,如果我们想从左向右调用函数就像上面的例子,我们可以用 `R.pipe`。下面两个例子用了 Rmmda。注意 Rmmda 有一个 `mean` 函数用来代替 `reduce` 。
|
||||
|
||||
```
|
||||
const vehicles = [
|
||||
{ make: 'Honda', model: 'CR-V', type: 'suv', price: 24045 },
|
||||
{ make: 'Honda', model: 'Accord', type: 'sedan', price: 22455 },
|
||||
{ make: 'Mazda', model: 'Mazda 6', type: 'sedan', price: 24195 },
|
||||
{ make: 'Mazda', model: 'CX-9', type: 'suv', price: 31520 },
|
||||
{ make: 'Toyota', model: '4Runner', type: 'suv', price: 34210 },
|
||||
{ make: 'Toyota', model: 'Sequoia', type: 'suv', price: 45560 },
|
||||
{ make: 'Toyota', model: 'Tacoma', type: 'truck', price: 24320 },
|
||||
{ make: 'Ford', model: 'F-150', type: 'truck', price: 27110 },
|
||||
{ make: 'Ford', model: 'Fusion', type: 'sedan', price: 22120 },
|
||||
{ make: 'Ford', model: 'Explorer', type: 'suv', price: 31660 }
|
||||
];
|
||||
|
||||
// Using `pipe` executes the functions from top-to-bottom.
|
||||
const averageSUVPrice1 = R.pipe(
|
||||
R.filter(v => v.type === 'suv'),
|
||||
R.map(v => v.price),
|
||||
R.mean
|
||||
)(vehicles);
|
||||
|
||||
console.log(averageSUVPrice1); // 33399
|
||||
|
||||
// Using `compose` executes the functions from bottom-to-top.
|
||||
const averageSUVPrice2 = R.compose(
|
||||
R.mean,
|
||||
R.map(v => v.price),
|
||||
R.filter(v => v.type === 'suv')
|
||||
)(vehicles);
|
||||
|
||||
console.log(averageSUVPrice2); // 33399
|
||||
```
|
||||
|
||||
使用函数式方法的优点是清楚地分开了数据(`vehicles`)和逻辑(函数 `filter`,`map` 和 `reduce`)。面向对象的代码相比之下把数据和函数用以方法的对象的形式混合在了一起。
|
||||
|
||||
### 柯里化
|
||||
|
||||
不规范地说,<ruby>柯里化<rt>currying</rt></ruby>是把一个接受 `n` 个参数的函数变成 `n` 个每个接受单个参数的函数的过程。函数的 `arity` 是它接受参数的个数。接受一个参数的函数是 `unary`,两个的是 `binary`,三个的是 `ternary`,`n` 个的是 `n-ary`。那么,我们可以把柯里化定义成将一个 `n-ary` 函数转换成 `n` 个 `unary` 函数的过程。让我们通过简单的例子开始,一个计算两个向量点积的函数。回忆一下线性代数,两个向量 `[a, b, c]` 和 `[x, y, z]` 的点积是 `ax + by + cz`。
|
||||
|
||||
```
|
||||
function dot(vector1, vector2) {
|
||||
return vector1.reduce((sum, element, index) => sum += element * vector2[index], 0);
|
||||
}
|
||||
|
||||
const v1 = [1, 3, -5];
|
||||
const v2 = [4, -2, -1];
|
||||
|
||||
console.log(dot(v1, v2)); // 1(4) + 3(-2) + (-5)(-1) = 4 - 6 + 5 = 3
|
||||
```
|
||||
|
||||
`dot` 函数是 binary,因为它接受两个参数;然而我们可以将它手动转换成两个 unary 函数,就像下面的例子。注意 `curriedDot` 是一个 unary 函数,它接受一个向量并返回另一个接受第二个向量的 unary 函数。
|
||||
|
||||
```
|
||||
function curriedDot(vector1) {
|
||||
return function(vector2) {
|
||||
return vector1.reduce((sum, element, index) => sum += element * vector2[index], 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Taking the dot product of any vector with [1, 1, 1]
|
||||
// is equivalent to summing up the elements of the other vector.
|
||||
const sumElements = curriedDot([1, 1, 1]);
|
||||
|
||||
console.log(sumElements([1, 3, -5])); // -1
|
||||
console.log(sumElements([4, -2, -1])); // 1
|
||||
```
|
||||
|
||||
很幸运,我们不需要把每一个函数都手动转换成柯里化以后的形式。[Ramda][96] 和 [lodash][97] 等库可以为我们做这些工作。实际上,它们是柯里化的混合形式。你既可以每次传递一个参数,也可以像原来一样一次传递所有参数。
|
||||
|
||||
```
|
||||
function dot(vector1, vector2) {
|
||||
return vector1.reduce((sum, element, index) => sum += element * vector2[index], 0);
|
||||
}
|
||||
|
||||
const v1 = [1, 3, -5];
|
||||
const v2 = [4, -2, -1];
|
||||
|
||||
// Use Ramda to do the currying for us!
|
||||
const curriedDot = R.curry(dot);
|
||||
|
||||
const sumElements = curriedDot([1, 1, 1]);
|
||||
|
||||
console.log(sumElements(v1)); // -1
|
||||
console.log(sumElements(v2)); // 1
|
||||
|
||||
// This works! You can still call the curried function with two arguments.
|
||||
console.log(curriedDot(v1, v2)); // 3
|
||||
```
|
||||
|
||||
Ramda 和 lodash 都允许你“跳过”一些变量之后再指定它们。它们使用置位符来做这些工作。因为点积的计算可以交换两项。传入向量的顺序不影响结果。让我们换一个例子来阐述如何使用一个置位符。Ramda 使用双下划线作为其置位符。
|
||||
|
||||
```
|
||||
const giveMe3 = R.curry(function(item1, item2, item3) {
|
||||
return `
|
||||
1: ${item1}
|
||||
2: ${item2}
|
||||
3: ${item3}
|
||||
`;
|
||||
});
|
||||
|
||||
const giveMe2 = giveMe3(R.__, R.__, 'French Hens'); // Specify the third argument.
|
||||
const giveMe1 = giveMe2('Partridge in a Pear Tree'); // This will go in the first slot.
|
||||
const result = giveMe1('Turtle Doves'); // Finally fill in the second argument.
|
||||
|
||||
console.log(result);
|
||||
// 1: Partridge in a Pear Tree
|
||||
// 2: Turtle Doves
|
||||
// 3: French Hens
|
||||
```
|
||||
|
||||
在我们结束探讨柯里化之前最后的议题是<ruby>偏函数应用<rt>partial application</rt></ruby>。偏函数应用和柯里化经常同时出场,尽管它们实际上是不同的概念。一个柯里化的函数还是柯里化的函数,即使没有给它任何参数。偏函数应用,另一方面是仅仅给一个函数传递部分参数而不是所有参数。柯里化是偏函数应用常用的方法之一,但是不是唯一的。
|
||||
|
||||
JavaScript 拥有一个内置机制可以不依靠柯里化来做偏函数应用。那就是 [function.prototype.bind][98] 方法。这个方法的一个特殊之处在于,它要求你将 `this` 作为第一个参数传入。 如果你不进行面向对象编程,那么你可以通过传入 `null` 来忽略 `this`。
|
||||
|
||||
```
|
||||
1function giveMe3(item1, item2, item3) {
|
||||
return `
|
||||
1: ${item1}
|
||||
2: ${item2}
|
||||
3: ${item3}
|
||||
`;
|
||||
}
|
||||
|
||||
const giveMe2 = giveMe3.bind(null, 'rock');
|
||||
const giveMe1 = giveMe2.bind(null, 'paper');
|
||||
const result = giveMe1('scissors');
|
||||
|
||||
console.log(result);
|
||||
// 1: rock
|
||||
// 2: paper
|
||||
// 3: scissors
|
||||
```
|
||||
|
||||
### 总结
|
||||
|
||||
我希望你享受探索 JavaScript 中函数式编程的过程。对一些人来说,它可能是一个全新的编程范式,但我希望你能尝试它。你会发现你的程序更易于阅读和调试。不变性还将允许你优化 Angular 和 React 的性能。
|
||||
|
||||
_这篇文章基于 Matt 在 OpenWest 的演讲 [JavaScript the Good-er Parts][77]. [OpenWest][78] ~~将~~在 6/12-15 ,2017 在 Salt Lake City, Utah 举行。_
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
Matt Banz - Matt 于 2008 年五月在犹他大学获得了数学学位毕业。一个月后他得到了一份 web 开发者的工作,他从那时起就爱上了它!在 2013 年,他在北卡罗莱纳州立大学获得了计算机科学硕士学位。他在 LDS 商学院和戴维斯学区社区教育计划教授 Web 课程。他现在是就职于 Motorola Solutions 公司的高级前端开发者。
|
||||
|
||||
--------------
|
||||
|
||||
via: https://opensource.com/article/17/6/functional-javascript
|
||||
|
||||
作者:[Matt Banz][a]
|
||||
译者:[trnhoe](https://github.com/trnhoe)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/battmanz
|
||||
[1]:https://opensource.com/tags/python?src=programming_resource_menu1
|
||||
[2]:https://opensource.com/tags/javascript?src=programming_resource_menu2
|
||||
[3]:https://opensource.com/tags/perl?src=programming_resource_menu3
|
||||
[4]:https://developers.redhat.com/?intcmp=7016000000127cYAAQ&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;src=programming_resource_menu4
|
||||
[5]:https://gist.github.com/battmanz/62fa0a78841aa0fe29d99e80ba8db2b1/raw/fd586c5da7c936235a6d99b11cb80c9c67e4deaf/pure-function-example.js
|
||||
[6]:https://gist.github.com/battmanz/62fa0a78841aa0fe29d99e80ba8db2b1#file-pure-function-example-js
|
||||
[7]:https://github.com/
|
||||
[8]:https://gist.github.com/battmanz/459c13138ea8e333fc6603ae688b7992/raw/ceda8a5c36c5bde69d4000b6ecb8fee98c9edcd3/impure-functions.js
|
||||
[9]:https://gist.github.com/battmanz/459c13138ea8e333fc6603ae688b7992#file-impure-functions-js
|
||||
[10]:https://github.com/
|
||||
[11]:https://gist.github.com/battmanz/bc13c4cf24b380cbd7b3f7b0c12ab8e4/raw/e2b6365e79def9b80bd7652cf15078d613ed686f/mutable-state.js
|
||||
[12]:https://gist.github.com/battmanz/bc13c4cf24b380cbd7b3f7b0c12ab8e4#file-mutable-state-js
|
||||
[13]:https://github.com/
|
||||
[14]:https://gist.github.com/battmanz/b65416550d62da94a69eea51c2678983/raw/6792dd568e0fc3e6b372d078735d5b74857dbae4/immutable-state.js
|
||||
[15]:https://gist.github.com/battmanz/b65416550d62da94a69eea51c2678983#file-immutable-state-js
|
||||
[16]:https://github.com/
|
||||
[17]:https://gist.github.com/battmanz/d32f2be485f4224121019ba4d070c25b/raw/f7318904effbef28e3a47989d4899ab019127c05/captured-mutable-object.js
|
||||
[18]:https://gist.github.com/battmanz/d32f2be485f4224121019ba4d070c25b#file-captured-mutable-object-js
|
||||
[19]:https://github.com/
|
||||
[20]:https://gist.github.com/battmanz/c5046c2d0af45938190e1178ab9cb007/raw/6cfd1b9644486f257357e611b2eeb148b3956baf/immutability-vs-reassignment.js
|
||||
[21]:https://gist.github.com/battmanz/c5046c2d0af45938190e1178ab9cb007#file-immutability-vs-reassignment-js
|
||||
[22]:https://github.com/
|
||||
[23]:https://gist.github.com/battmanz/a400ad93d9922fdc3a2ff87c0bf7da68/raw/6771481278c95942b4627a709c377f62856a0a3a/array-mutator-method-replacement.js
|
||||
[24]:https://gist.github.com/battmanz/a400ad93d9922fdc3a2ff87c0bf7da68#file-array-mutator-method-replacement-js
|
||||
[25]:https://github.com/
|
||||
[26]:https://gist.github.com/battmanz/9ffbac3c18c6cf33d97a4ad511129e94/raw/433f65ebe3d2a7fda7ac1f434c2d56ad98a04ce0/map-mutator-method-replacement.js
|
||||
[27]:https://gist.github.com/battmanz/9ffbac3c18c6cf33d97a4ad511129e94#file-map-mutator-method-replacement-js
|
||||
[28]:https://github.com/
|
||||
[29]:https://gist.github.com/battmanz/d42d3224c99d76d780f68daaa6a87338/raw/111578801a4120726a369a43f87d33a64b39dc83/set-mutator-method-replacement.js
|
||||
[30]:https://gist.github.com/battmanz/d42d3224c99d76d780f68daaa6a87338#file-set-mutator-method-replacement-js
|
||||
[31]:https://github.com/
|
||||
[32]:https://opensource.com/file/357111
|
||||
[33]:https://gist.github.com/battmanz/7cec8c2f22ee55f60dd0c478236892de/raw/2cad9d5441ebef816e3d1cbc03af883451e68dc3/immutable-js-demo.js
|
||||
[34]:https://gist.github.com/battmanz/7cec8c2f22ee55f60dd0c478236892de#file-immutable-js-demo-js
|
||||
[35]:https://github.com/
|
||||
[36]:https://gist.github.com/battmanz/99325b35a147c37b20f5652785430381/raw/28a6dc814aaf7d023cebefb6d7f694d76f99f9da/function-composition-basic.js
|
||||
[37]:https://gist.github.com/battmanz/99325b35a147c37b20f5652785430381#file-function-composition-basic-js
|
||||
[38]:https://github.com/
|
||||
[39]:https://gist.github.com/battmanz/e250ae6c628550f6f0ac718d046ea74e/raw/a0c22d4a1afaf68c6297df3de4736c62e58cb028/function-composition-elegant.js
|
||||
[40]:https://gist.github.com/battmanz/e250ae6c628550f6f0ac718d046ea74e#file-function-composition-elegant-js
|
||||
[41]:https://github.com/
|
||||
[42]:https://gist.github.com/battmanz/bc225959e1328e73b08c1fe4ab59b630/raw/648090bc2d40f4fc6e137f7426b803337c5fa3bb/iterative-factorial.js
|
||||
[43]:https://gist.github.com/battmanz/bc225959e1328e73b08c1fe4ab59b630#file-iterative-factorial-js
|
||||
[44]:https://github.com/
|
||||
[45]:https://gist.github.com/battmanz/63961ad6fa380463785b69a1b34e7997/raw/550f6922ecc5adc21ab38f281a788e286abc107a/recursive-factorial.js
|
||||
[46]:https://gist.github.com/battmanz/63961ad6fa380463785b69a1b34e7997#file-recursive-factorial-js
|
||||
[47]:https://github.com/
|
||||
[48]:https://opensource.com/file/357126
|
||||
[49]:https://opensource.com/file/357131
|
||||
[50]:https://gist.github.com/battmanz/26ecb25247a01030ca4ab0cd1ebfc5b3/raw/e3aaa078b9d262dcd35e534145cba0f52b5d5d67/factorial-tail-recursion.js
|
||||
[51]:https://gist.github.com/battmanz/26ecb25247a01030ca4ab0cd1ebfc5b3#file-factorial-tail-recursion-js
|
||||
[52]:https://github.com/
|
||||
[53]:https://opensource.com/file/357116
|
||||
[54]:https://opensource.com/file/357121
|
||||
[55]:https://gist.github.com/battmanz/9eda50362457362f2d8a28384bf1adfc/raw/bb7252ef116d5a2ae430e68c6b5650d1dd6f44a4/built-in-higher-order-functions.js
|
||||
[56]:https://gist.github.com/battmanz/9eda50362457362f2d8a28384bf1adfc#file-built-in-higher-order-functions-js
|
||||
[57]:https://github.com/
|
||||
[58]:https://gist.github.com/battmanz/bee10f02a076f064f72e20cd4aea6b85/raw/bd3de649887d515f4166290802d4a3d89f80210c/composing-higher-order-functions.js
|
||||
[59]:https://gist.github.com/battmanz/bee10f02a076f064f72e20cd4aea6b85#file-composing-higher-order-functions-js
|
||||
[60]:https://github.com/
|
||||
[61]:https://gist.github.com/battmanz/e28311f765a18fc6a841201912422d60/raw/e3e5489652e1f4815bf810f98b4aba6f5ec934e6/dot-product.js
|
||||
[62]:https://gist.github.com/battmanz/e28311f765a18fc6a841201912422d60#file-dot-product-js
|
||||
[63]:https://github.com/
|
||||
[64]:https://gist.github.com/battmanz/3a3694f87b9c48ac0752e8fe3e3a0b8d/raw/c886e5ea1fd7b6e4a130925634c1d1d6f8ffc689/manual-currying.js
|
||||
[65]:https://gist.github.com/battmanz/3a3694f87b9c48ac0752e8fe3e3a0b8d#file-manual-currying-js
|
||||
[66]:https://github.com/
|
||||
[67]:https://gist.github.com/battmanz/3335a949ea88b8d969c359774b76ee35/raw/99a2997e4609e9ca294d7b58e330f2cf6dbaefcb/fancy-currying.js
|
||||
[68]:https://gist.github.com/battmanz/3335a949ea88b8d969c359774b76ee35#file-fancy-currying-js
|
||||
[69]:https://github.com/
|
||||
[70]:https://gist.github.com/battmanz/ea5e1f34cf468214039557c78e43a9b5/raw/9b6556bd9111efd03dd69bee5596c29002e2279a/currying-placeholder.js
|
||||
[71]:https://gist.github.com/battmanz/ea5e1f34cf468214039557c78e43a9b5#file-currying-placeholder-js
|
||||
[72]:https://github.com/
|
||||
[73]:https://gist.github.com/battmanz/dadae797f9d3cfcce85af0aecfd6f6e3/raw/3d248b9810cab7d02ecd050ec549247499de6f31/partial-application-using-bind.js
|
||||
[74]:https://gist.github.com/battmanz/dadae797f9d3cfcce85af0aecfd6f6e3#file-partial-application-using-bind-js
|
||||
[75]:https://github.com/
|
||||
[76]:https://opensource.com/article/17/6/functional-javascript?rate=rGE6lsdTzq9H9vSv4jiF7pTN9hYG5Ehm_GsfSJbJsDM
|
||||
[77]:https://www.openwest.org/schedule/#talk-5
|
||||
[78]:https://www.openwest.org/
|
||||
[79]:https://opensource.com/user/146401/feed
|
||||
[80]:https://www.flickr.com/photos/jurvetson/882193732/
|
||||
[81]:https://brendaneich.com/2008/04/popularity/
|
||||
[82]:https://angular-2-training-book.rangle.io/handout/change-detection/change_detection_strategy_onpush.html
|
||||
[83]:https://facebook.github.io/react/docs/optimizing-performance.html#the-power-of-not-mutating-data
|
||||
[84]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
|
||||
[85]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
|
||||
[86]:https://facebook.github.io/immutable-js/
|
||||
[87]:https://facebook.github.io/immutable-js/docs/#/List
|
||||
[88]:https://facebook.github.io/immutable-js/docs/#/Stack
|
||||
[89]:https://facebook.github.io/immutable-js/docs/#/Map
|
||||
[90]:https://facebook.github.io/immutable-js/docs/#/Set
|
||||
[91]:https://en.wikipedia.org/wiki/Persistent_data_structure
|
||||
[92]:http://ramdajs.com/
|
||||
[93]:https://github.com/lodash/lodash/wiki/FP-Guide
|
||||
[94]:https://math.stackexchange.com/questions/20969/prove-0-1-from-first-principles
|
||||
[95]:https://github.com/tc39/proposal-ptc-syntax#syntactic-tail-calls-stc
|
||||
[96]:http://ramdajs.com/docs/#curry
|
||||
[97]:https://lodash.com/docs/4.17.4#curry
|
||||
[98]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
|
||||
[99]:https://opensource.com/users/battmanz
|
||||
@@ -0,0 +1,159 @@
|
||||
用 Linux、Python 和树莓派酿制啤酒
|
||||
============================================================
|
||||
|
||||
> 怎样在家用 Python 和树莓派搭建一个家用便携的自制酿啤酒装置
|
||||
|
||||

|
||||
|
||||
大约十年前我开始酿制自制啤酒,和许多自己酿酒的人一样,我开始在厨房制造提纯啤酒。这需要一些设备并且做出来后确实是好的啤酒,最终,我用一个放入了所有大麦的大贮藏罐作为我的麦芽浆桶。几年之后我一次酿制过 5 加仑啤酒,但是酿制 10 加仑时也会花费同样的时间和效用(只是容器比之前大些),之前我就是这么做的。容量提升到 10 加仑之后,我偶然看到了 [StrangeBrew Elsinore][38] ,我意识到我真正需要的是将整个酿酒过程转换成全电子化的,用树莓派来运行它。
|
||||
|
||||
建造自己的家用电动化酿酒系统需要大量这方面的技术信息,许多学习酿酒的人是在 [TheElectricBrewery.com][28] 这个网站起步的,只不过将那些控制版搭建在一起是十分复杂的,尽管最简单的办法在这个网站上总结的很好。当然你也能用[一个小成本的方法][26]并且依旧可以得到相同的结果 —— 用一个热水壶和热酒容器通过一个 PID 控制器来加热你的酿酒原料。但是我认为这有点太无聊(这也意味着你不能体验到完整的酿酒过程)。
|
||||
|
||||
### 需要用到的硬件
|
||||
|
||||
在我开始我的这个项目之前, 我决定开始买零件,我最基础的设计是一个可以将液体加热到 5500 瓦的热酒容器(HLT)和开水壶,加一个活底的麦芽浆桶,我通过一个 50 英尺的不锈钢线圈在热酒容器里让泵来再循环麦芽浆(["热量交换再循环麦芽浆系统, 也叫 HERMS][27])。同时我需要另一个泵来在热酒容器里循环水,并且把水传输到麦芽浆桶里,整个电子部件全部是用树莓派来控制的。
|
||||
|
||||
建立我的电子酿酒系统并且尽可能的自动化意味着我需要以下的组件:
|
||||
|
||||
* 一个 5500 瓦的电子加热酒精容器(HLT)
|
||||
* 能够放入加热酒精容器里的 50 英尺(0.5 英寸)的不锈钢线圈(热量交换再循环麦芽浆系统)
|
||||
* 一个 5500 瓦的电子加热水壶
|
||||
* 多个固态继电器加热开关
|
||||
* 2 个高温食品级泵
|
||||
* 泵的开关用继电器
|
||||
* 可拆除装置和一个硅管
|
||||
* 不锈钢球阀
|
||||
* 一个测量温度的探针
|
||||
* 很多线
|
||||
* 一个来容纳这些配件的电路盒子
|
||||
|
||||

|
||||
|
||||
*酿酒系统 (photo by Christopher Aedo. [CC BY-SA 4.0)][5]*
|
||||
|
||||
建立酿酒系统的电气化方面的细节 [The Electric Brewery][28] 这个网站概括的很好,这里我不再重复,当你计划用树莓派代替这个 PID 控制器的话,你可以读以下的建议。
|
||||
|
||||
一个重要的事情需要注意,固态继电器(SSR)信号电压,许多教程建议使用一个 12 伏的固态继电器来关闭电路,树莓派的 GPIO 针插口只支持 3 伏输出电压,然而,必须购买继电器将电压变为 3 伏。
|
||||
|
||||

|
||||
|
||||
*Inkbird SSR (photo by Christopher Aedo. [CC BY-SA 4.0)][6]*
|
||||
|
||||
要运行酿酒系统,你的树莓派必须做两个关键事情:测量来自几个不同位置的温度,用继电器开关来控制加热元件,树莓派很容易来处理这些任务。
|
||||
|
||||
这里有一些不同的方法来将温度传感器连到树莓派上,但是我找到了最方便的方法用[单总线][29]。这就可以让多个传感器分享相同的线路(实际上是三根线),这三根线可以使酿酒系统的多个设备更方便的工作,如果你要从网上找一个防水的 DS18B20 温度传感器,你可以会找到很多选择。我用的是[日立 DS18B20 防水温度传感器][30]。
|
||||
|
||||
要控制加热元件,树莓派包括了几个用来软件寻址的总线扩展器(GPIO),它会通过在某个文件写入 0 或者 1 让你发送3.3v 的电压到一个继电器,在我第一次了解树莓派是怎样工作的时候,这个[用 GPIO 驱动继电器的树莓派教程][46]对我来说是最有帮助的,总线扩展器控制着多个固态继电器,通过酿酒软件来直接控制加热元件的开关。
|
||||
|
||||
我首先将所有部件放到这个电路盒子,因为这将成为一个滚动的小车,我要让它便于移动,而不是固定不动的,如果我有一个店(比如说在车库、工具房、或者地下室),我需要要用一个装在墙上的更大的电路盒,而现在我找到一个大小正好的[防水工程盒子][31],能放进每件东西,最后它成为小巧紧凑工具盒,并且能够工作。在左下角是和树莓派连接的为总线扩展器到单总线温度探针和[固态继电器][32]的扩展板。
|
||||
|
||||
要保持 240v 的固态继电器温度不高,我在盒子上切了个洞,在盒子的外面用 CPU 降温凝胶把[铜片散热片][33]安装到盒子外面的热槽之间。它工作的很好,盒子里没有温度上的问题了,在盒子盖上我放了两个开关为 120v 的插座,加两个240v 的 led 来显示加热元件是否通电。我用干燥器的插座和插头,所以可以很容易的断开电热水壶的连接。首次尝试每件事情都工作正常。(第一次绘制电路图必有回报)
|
||||
|
||||
这个照片来自“概念”版,最终生产系统应该有两个以上的固态继电器,以便 240v 的电路两个针脚能够切换,另外我将通过软件来切换泵的开关。现在通过盒子前面的物理开关控制它们,但是也很容易用继电器控制它们。
|
||||
|
||||

|
||||
|
||||
*控制盒子 (photo by Christopher Aedo. [CC BY-SA 4.0)][7]*
|
||||
|
||||
唯一剩下有点棘手的事情是温度探针的压合接头,这个探针安装在加热酒精容器和麦芽浆桶球形的最底部阀门前的 T 字型接头上。当液体流过温度传感器,温度可以准确显示。我考虑加一个套管到热水壶里,但是对于我的酿造工艺没有什么用。最后,我买到了[四分之一英寸的压合接头][34],它们工作完美。
|
||||
|
||||
### 软件
|
||||
|
||||
一旦硬件整理好,我就有时间来处理软件了,我在树莓派上跑了最新的 [Raspbian 发行版][35],操作系统方面没有什么特别的。
|
||||
|
||||
我开始使用 [Strangebrew Elsinore][36] 酿酒软件,当我的朋友问我是否我听说过 [Hosehead][37](一个基于树莓派的酿酒控制器),我找到了 [Strangebrew Elsinore][36] 。我认为 [Hosehead][37] 很棒,但我并不是要买一个酿酒控制器,而是要挑战自己,搭建一个自己的。
|
||||
|
||||
设置 [Strangebrew Elsinore][36] 很简单,其[文档][38]直白,没有遇到任何的问题。尽管 Strangebrew Elsinore 工作的很好,但在我的一代树莓派上运行 java 有时是费力的,不止崩溃一次。我看到这个软件开发停顿也很伤心,似乎他们也没有更多贡献者的大型社区(尽管有很多人还在用它)。
|
||||
|
||||
#### CraftBeerPi
|
||||
|
||||
之后我偶然遇到了一个用 Python 写的 [CraftbeerPI][39],它有活跃的贡献者支持的开发社区。原作者(也是当前维护者) Manuel Fritsch 在贡献和反馈问题处理方面做的很好。克隆[这个仓库][40]然后开始只用了我一点时间。其 README 文档也是一个连接 DS1820 温度传感器的好例子,同时也有关于硬件接口到树莓派或者[芯片电脑][41] 的注意事项。
|
||||
|
||||
在启动的时候,CraftbeerPI 引导用户通过一个设置过程来发现温度探针是否可用,并且让你指定哪个 GPIO 总线扩展器指针来管理树莓派上哪个配件。
|
||||
|
||||

|
||||
|
||||
*CraftBeerPi (photo by Christopher Aedo. [CC BY-SA 4.0)][8]*
|
||||
|
||||
用这个系统进行自制酿酒是容易的,我能够依靠它掌握可靠的温度,我能输入多个温度段来控制麦芽浆温度,用CraftbeerPi 酿酒的日子有一点点累,但是我很高兴用传统的手工管理丙烷燃烧器的“兴奋”来换取这个系统的有效性和持续性。
|
||||
|
||||
CraftBeerPI 的用户友好性鼓舞我设置了另一个控制器来运行“发酵室”。就我来说,那是一个二手冰箱,我用了 50 美元加上放在里面的 25 美元的加热器。CraftBeerPI 很容易控制电器元件的冷热,你也能够设置多个温度阶段。举个例子,这个图表显示我最近做的 IPA 进程的发酵温度。发酵室发酵麦芽汁在 67F° 的温度下需要 4 天,然后每 12 小时上升一度直到温度到达 72F°。剩下两天温度保持不变是为了双乙酰生成。之后 5 天温度降到 65F°,这段时间是让啤酒变“干”,最后啤酒发酵温度直接降到 38F°。CraftBeerPI 可以加入各个阶段,让软件管理发酵更加容易。
|
||||
|
||||

|
||||
|
||||
*SIPA 发酵设置 (photo by Christopher Aedo. [CC BY-SA 4.0)][9]*
|
||||
|
||||
我也试验过用[液体比重计][42]来对酵啤酒的比重进行监测,通过蓝牙连接的浮动传感器可以达到。有一个整合的计划能让 CraftbeerPi 很好工作,现在它记录这些比重数据到谷歌的电子表格里。一旦这个液体比重计能连接到发酵控制器,设置的自动发酵设置会基于酵母的活动性直接运行且更加容易,而不是在 4 天内完成主要发酵,可以在比重稳定 24 小时后设定温度。
|
||||
|
||||
像这样的一些项目,构想并计划改进和增加组件是很容易,不过,我很高兴今天经历过的事情。我用这种装置酿造了很多啤酒,每次都能达到预期的麦芽汁比率,而且啤酒一直都很美味。我的最重要的消费者 —— 就是我!很高兴我可以随时饮用。
|
||||
|
||||

|
||||
|
||||
*随时饮用 (photo by Christopher Aedo. [CC BY-SA 4.0)][10]*
|
||||
|
||||
这篇文章基于 Christopher 的开放的西部的讲话《用Linux、Python 和树莓派酿制啤酒》。
|
||||
|
||||
(题图:[Quinn Dombrowski][21]. Modified by Opensource.com. [CC BY-SA 4.0][22])
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
Christopher Aedo 从他的学生时代就从事并且贡献于开源软件事业。最近他在 IBM 领导一个极棒的上游开发者团队,同时他也是开发者拥护者。当他不再工作或者实在会议室演讲的时候,他可能在波特兰市俄勒冈州用树莓派酿制和发酵一杯美味的啤酒。
|
||||
|
||||
----
|
||||
via: https://opensource.com/article/17/7/brewing-beer-python-and-raspberry-pi
|
||||
|
||||
作者:[Christopher Aedo][a]
|
||||
译者:[hwlife](https://github.com/hwlife)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/docaedo
|
||||
[1]:https://opensource.com/tags/raspberry-pi?src=raspberry_pi_resource_menu1
|
||||
[2]:https://opensource.com/resources/what-raspberry-pi?src=raspberry_pi_resource_menu2
|
||||
[3]:https://opensource.com/article/16/12/getting-started-raspberry-pi?src=raspberry_pi_resource_menu3
|
||||
[4]:https://opensource.com/article/17/2/raspberry-pi-submit-your-article?src=raspberry_pi_resource_menu4
|
||||
[5]:https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[6]:https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[7]:https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[8]:https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[9]:https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[10]:https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[11]:https://opensource.com/file/358661
|
||||
[12]:https://opensource.com/file/358666
|
||||
[13]:https://opensource.com/file/358676
|
||||
[14]:https://opensource.com/file/359061
|
||||
[15]:https://opensource.com/file/358681
|
||||
[16]:https://opensource.com/file/359071
|
||||
[17]:https://opensource.com/article/17/7/brewing-beer-python-and-raspberry-pi?rate=fbKzT1V9gqGsmNCTuQIashC1xaHT5P_2LUaeTn6Kz1Y
|
||||
[18]:https://www.openwest.org/custom/description.php?id=139
|
||||
[19]:https://www.openwest.org/
|
||||
[20]:https://opensource.com/user/145976/feed
|
||||
[21]:https://www.flickr.com/photos/quinndombrowski/
|
||||
[22]:https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[23]:https://github.com/DougEdey/SB_Elsinore_Server
|
||||
[24]:https://opensource.com/tags/raspberry-pi
|
||||
[25]:http://www.theelectricbrewery.com/
|
||||
[26]:http://www.instructables.com/id/Electric-Brewery-Control-Panel-on-the-Cheap/
|
||||
[27]:https://byo.com/hops/item/1325-rims-and-herms-brewing-advanced-homebrewing
|
||||
[28]:http://theelectricbrewery.com/
|
||||
[29]:https://en.wikipedia.org/wiki/1-Wire
|
||||
[30]:https://smile.amazon.com/gp/product/B018KFX5X0/
|
||||
[31]:http://amzn.to/2hupFCr
|
||||
[32]:http://amzn.to/2hL8JDS
|
||||
[33]:http://amzn.to/2i4DYwy
|
||||
[34]:https://www.brewershardware.com/CF1412.html
|
||||
[35]:https://www.raspberrypi.org/downloads/raspbian/
|
||||
[36]:https://github.com/DougEdey/SB_Elsinore_Server
|
||||
[37]:https://brewtronix.com/
|
||||
[38]:http://dougedey.github.io/SB_Elsinore_Server/
|
||||
[39]:http://www.craftbeerpi.com/
|
||||
[40]:https://github.com/manuel83/craftbeerpi
|
||||
[41]:https://www.nextthing.co/pages/chip
|
||||
[42]:https://tilthydrometer.com/
|
||||
[43]:https://opensource.com/users/docaedo
|
||||
[44]:https://opensource.com/users/docaedo
|
||||
[45]:https://opensource.com/article/17/7/brewing-beer-python-and-raspberry-pi#comments
|
||||
[46]:http://www.susa.net/wordpress/2012/06/raspberry-pi-relay-using-gpio/
|
||||
@@ -0,0 +1,339 @@
|
||||
GitHub 的 MySQL 基础架构自动化测试
|
||||
============================================================
|
||||
|
||||
我们 MySQL 数据库基础架构是 Github 关键组件。 MySQL 提供 Github.com、 GitHub 的 API 和验证等等的服务。每一次的 `git` 请求都以某种方式触及 MySQL。我们的任务是保持数据的可用性,并保持其完整性。即使我们 MySQL 集群是按流量分配的,但是我们还是需要执行深度清理、即时更新、在线<ruby>模式<rt>schema</rt></ruby>迁移、集群拓扑重构、<ruby>连接池化<rt>pooling</rt></ruby>和负载平衡等任务。 我们建有基础架构来自动化测试这些操作,在这篇文章中,我们将分享几个例子,来说明我们是如何通过持续测试打造我们的基础架构的。这是让我们一梦到天亮的根本保障。
|
||||
|
||||
### 备份
|
||||
|
||||
没有比备份数据更重要的了,如果您没有备份数据库,在它出事前这可能并不是什么问题。Percona 的 [Xtrabackup][37] 是我们一直用来完整备份 MySQL 数据库的工具。如果有专门需要备份的数据,我们就会备份到另一个专门备份数据的服务器上。
|
||||
|
||||
除了完整的二进制备份外,我们每天还会多次运行逻辑备份。这些备份数据可以让我们的工程师获取到最新的数据副本。有时候,他们希望从表中获取一整套数据,以便他们可以在一个生产级规模的表上测试索引的修改,或查看特定时间以来的数据。Hubot 可以让我们恢复备份的表,并且当表准备好使用时会通知我们。
|
||||
|
||||
**tomkrouper**
|
||||
|
||||
```
|
||||
.mysql backup-list locations
|
||||
```
|
||||
|
||||
**Hubot**
|
||||
|
||||
```
|
||||
+-----------+------------+---------------+---------------------+---------------------+----------------------------------------------+
|
||||
| Backup ID | Table Name | Donor Host | Backup Start | Backup End | File Name |
|
||||
+-----------+------------+---------------+---------------------+---------------------+----------------------------------------------+
|
||||
| 1699494 | locations | db-mysql-0903 | 2017-07-01 22:09:17 | 2017-07-01 22:09:17 | backup-mycluster-locations-1498593122.sql.gz |
|
||||
| 1699133 | locations | db-mysql-0903 | 2017-07-01 16:11:37 | 2017-07-01 16:11:39 | backup-mycluster-locations-1498571521.sql.gz |
|
||||
| 1698772 | locations | db-mysql-0903 | 2017-07-01 10:09:21 | 2017-07-01 10:09:22 | backup-mycluster-locations-1498549921.sql.gz |
|
||||
| 1698411 | locations | db-mysql-0903 | 2017-07-01 04:12:32 | 2017-07-01 04:12:32 | backup-mycluster-locations-1498528321.sql.gz |
|
||||
| 1698050 | locations | db-mysql-0903 | 2017-06-30 22:18:23 | 2017-06-30 22:18:23 | backup-mycluster-locations-1498506721.sql.gz |
|
||||
| ...
|
||||
| 1262253 | locations | db-mysql-0088 | 2016-08-01 01:58:51 | 2016-08-01 01:58:54 | backup-mycluster-locations-1470034801.sql.gz |
|
||||
| 1064984 | locations | db-mysql-0088 | 2016-04-04 13:07:40 | 2016-04-04 13:07:43 | backup-mycluster-locations-1459494001.sql.gz |
|
||||
+-----------+------------+---------------+---------------------+---------------------+----------------------------------------------+
|
||||
```
|
||||
|
||||
**tomkrouper**
|
||||
|
||||
```
|
||||
.mysql restore 1699133
|
||||
```
|
||||
|
||||
**Hubot**
|
||||
|
||||
```
|
||||
A restore job has been created for the backup job 1699133. You will be notified in #database-ops when the restore is complete.
|
||||
```
|
||||
|
||||
**Hubot**
|
||||
|
||||
```
|
||||
@tomkrouper: the locations table has been restored as locations_2017_07_01_16_11 in the restores database on db-mysql-0482
|
||||
```
|
||||
|
||||
数据被加载到非生产环境的数据库,该数据库可供请求该次恢复的工程师访问。
|
||||
|
||||
我们保留数据的“备份”的最后一个方法是使用<ruby>[延迟副本][38]<rt>delayed replica</rt></ruby>。这与其说是备份,不如说是保护。对于每个生产集群,我们有一个延迟 4 个小时复制的主机。如果运行了一个不该运行的请求,我们可以在 chatops 中运行 `mysql panic` 。这将导致我们所有的延迟副本立即停止复制。这也将给值班 DBA 发送消息。从而我们可以使用延迟副本来验证是否有问题,并快速前进到二进制日志的错误发生之前的位置。然后,我们可以将此数据恢复到主服务器,从而恢复数据到该时间点。
|
||||
|
||||
备份固然好,但如果发生了一些未知或未捕获的错误破坏它们,它们就没有价值了。让脚本恢复备份的好处是它允许我们通过 cron 自动执行备份验证。我们为每个集群设置了一个专用的主机,用于运行最新备份的恢复。这样可以确保备份运行正常,并且我们能够从备份中检索数据。
|
||||
|
||||
根据数据集大小,我们每天运行几次恢复。恢复的服务器被加入到复制工作流,并通过复制保持数据更新。这测试不仅让我们得到了可恢复的备份,而且也让我们得以正确地确定备份的时间点,并且可以从该时间点进一步应用更改。如果恢复过程中出现问题,我们会收到通知。
|
||||
|
||||
我们还追踪恢复所需的时间,所以我们知道在紧急情况下建立新的副本或还原需要多长时间。
|
||||
|
||||
以下是由 Hubot 在我们的机器人聊天室中输出的自动恢复过程。
|
||||
|
||||
**Hubot**
|
||||
|
||||
```
|
||||
gh-mysql-backup-restore: db-mysql-0752: restore_log.id = 4447
|
||||
gh-mysql-backup-restore: db-mysql-0752: Determining backup to restore for cluster 'prodcluster'.
|
||||
gh-mysql-backup-restore: db-mysql-0752: Enabling maintenance mode
|
||||
gh-mysql-backup-restore: db-mysql-0752: Setting orchestrator downtime
|
||||
gh-mysql-backup-restore: db-mysql-0752: Disabling Puppet
|
||||
gh-mysql-backup-restore: db-mysql-0752: Stopping MySQL
|
||||
gh-mysql-backup-restore: db-mysql-0752: Removing MySQL files
|
||||
gh-mysql-backup-restore: db-mysql-0752: Running gh-xtrabackup-restore
|
||||
gh-mysql-backup-restore: db-mysql-0752: Restore file: xtrabackup-notify-2017-07-02_0000.xbstream
|
||||
gh-mysql-backup-restore: db-mysql-0752: Running gh-xtrabackup-prepare
|
||||
gh-mysql-backup-restore: db-mysql-0752: Starting MySQL
|
||||
gh-mysql-backup-restore: db-mysql-0752: Update file ownership
|
||||
gh-mysql-backup-restore: db-mysql-0752: Upgrade MySQL
|
||||
gh-mysql-backup-restore: db-mysql-0752: Stopping MySQL
|
||||
gh-mysql-backup-restore: db-mysql-0752: Starting MySQL
|
||||
gh-mysql-backup-restore: db-mysql-0752: Backup Host: db-mysql-0034
|
||||
gh-mysql-backup-restore: db-mysql-0752: Setting up replication
|
||||
gh-mysql-backup-restore: db-mysql-0752: Starting replication
|
||||
gh-mysql-backup-restore: db-mysql-0752: Replication catch-up
|
||||
gh-mysql-backup-restore: db-mysql-0752: Restore complete (replication running)
|
||||
gh-mysql-backup-restore: db-mysql-0752: Enabling Puppet
|
||||
gh-mysql-backup-restore: db-mysql-0752: Disabling maintenance mode
|
||||
gh-mysql-backup-restore: db-mysql-0752: Setting orchestrator downtime
|
||||
gh-mysql-backup-restore: db-mysql-0752: Restore process complete.
|
||||
```
|
||||
|
||||
还有一件我们可以使用备份做的事情是向一组现有的 MySQL 服务器添加一个新的副本。我们初始化一个新的服务器,一旦我们得知它准备就绪,我们就可以开始恢复该特定集群的最新备份。我们有一个脚本,可以运行所有需要手动执行的恢复命令。我们的自动恢复系统基本上使用的是相同的脚本。这简化了系统构建过程,并允许我们在一个启动运行的主机上使用几个对话命令(chatops)来替代一堆手动执行命令。下面显示的是在该对话(chatops)中手动执行的恢复:
|
||||
|
||||
**jessbreckenridge**
|
||||
```
|
||||
.mysql backup-restore -H db-mysql-0007 -o -r magic_word=daily_rotating_word
|
||||
```
|
||||
**Hubot**
|
||||
```
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Determining backup to restore for cluster 'mycluster'.
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: restore_log.id = 4449
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Enabling maintenance mode
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Setting orchestrator downtime
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Disabling Puppet
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Stopping MySQL
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Removing MySQL files
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Running gh-xtrabackup-restore
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Restore file: xtrabackup-mycluster-2017-07-02_0015.xbstream
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Running gh-xtrabackup-prepare
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Update file ownership
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Starting MySQL
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Upgrade MySQL
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Stopping MySQL
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Starting MySQL
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Setting up replication
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Starting replication
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Backup Host: db-mysql-0201
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Replication catch-up
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Replication behind by 4589 seconds, waiting 1800 seconds before next check.
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Restore complete (replication running)
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Enabling puppet
|
||||
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Disabling maintenance mode
|
||||
```
|
||||
|
||||
### 故障转移
|
||||
|
||||
[我们使用协调器][40] 来为<ruby>主服务器<rt>master</rt></ruby>和<ruby>中间服务器<rt>intermediate master</rt></ruby>执行自动化故障切换。我们期望<ruby>协调器<rt>orchestrator</rt></ruby>能够正确检测主服务器故障,指定一个副本进行晋升,在所指定的副本下修复拓扑,完成晋升。我们预期 VIP(虚拟 IP)、连接池可以相应地进行变化、客户端进行重连、puppet 在晋升后的主服务器上运行基本组件等等。故障转移是一项复杂的任务,涉及到我们基础架构的许多方面。
|
||||
|
||||
为了建立对我们的故障转移的信赖,我们建立了一个_类生产环境_的测试集群,并且我们不断地崩溃它来观察故障转移情况。
|
||||
|
||||
这个_类生产环境_的测试集群是一套复制环境,与我们的生产集群的各个方面都相同:硬件类型、操作系统、MySQL 版本、网络环境、VIP、puppet 配置、[haproxy 设置][41] 等。与生产集群唯一不同的是它不发送/接收生产流量。
|
||||
|
||||
我们在测试集群上模拟写入负载,同时避免复制滞后。写入负载不会太大,但是有一些有意地写入相同数据集的竞争请求。这在正常情况下并不是很有用,但是事实证明这在故障转移中是有用的,我们将会稍后简要描述它。
|
||||
|
||||
我们的测试集群有来自三个数据中心的典型的服务器。我们希望故障转移能够从同一个数据中心内晋升替代副本。我们希望在这样的限制下尽可能多地恢复副本。我们要求尽可能地实现这两者。协调器对拓扑结构没有<ruby>先验假定<rt>prior assumption</rt></ruby>;它必须依据崩溃时的状态作出反应。
|
||||
|
||||
然而,我们有兴趣创建各种复杂而多变的故障恢复场景。我们的故障转移测试脚本为故障转移提供了基础:
|
||||
|
||||
* 它能够识别现有的主服务器
|
||||
* 它能够重构拓扑结构,来代表主服务器下的所有的三个数据中心。不同的数据中心具有不同的网络延迟,并且预期会在不同的时间对主机崩溃做出反应。
|
||||
* 能够选择崩溃方式。可以选择干掉主服务器(`kill -9`)或网络隔离(比较好的方式: `iptables -j REJECT` 或无响应的方式: `iptables -j DROP`)方式。
|
||||
|
||||
脚本通过选择的方法使主机崩溃,并等待协调器可靠地检测到崩溃然后执行故障转移。虽然我们期望检测和晋升在 30 秒钟内完成,但脚本会稍微放宽这一期望,并在查找故障转移结果之前休眠一段指定的时间。然后它将检查:
|
||||
|
||||
* 一个新的(不同的)主服务器是否到位
|
||||
* 集群中有足够的副本
|
||||
* 主服务器是可写的
|
||||
* 对主服务器的写入在副本上可见
|
||||
* 内部服务发现项已更新(如预期般识别到新的主服务器;移除旧的主服务器)
|
||||
* 其他内部检查
|
||||
|
||||
这些测试可以证实故障转移是成功的,不仅是 MySQL 级别的,而是在更大的基础设施范围内成功的。VIP 被赋予;特定的服务已经启动;信息到达了应该去的地方。
|
||||
|
||||
该脚本进一步继续恢复那个失败的服务器:
|
||||
|
||||
* 从备份恢复它,从而隐含地测试了我们的备份/恢复过程
|
||||
* 验证服务器配置是否符合预期(该服务器不再认为其是主服务器)
|
||||
* 将其加入到复制集群,期望找到在主服务器上写入的数据
|
||||
|
||||
看一下以下可视化的计划的故障转移测试:从运行良好的群集,到在某些副本上发现问题,诊断主服务器(`7136`)是否死机,选择一个服务器(`a79d`)来晋升,重构该服务器下的拓扑,晋升它(故障切换成功),恢复失败的(原)主服务器并将其放回群集。
|
||||
|
||||

|
||||
|
||||
#### 测试失败怎么样?
|
||||
|
||||
我们的测试脚本使用了一种“停止世界”的方法。任何故障切换组件中的单个故障都将导致整个测试失败,因此在有人解决该问题之前,无法进行任何进一步的自动化测试。我们会得到警报,并检查状态和日志进行处理。
|
||||
|
||||
脚本将各种情况下失败,如不可接受的检测或故障转移时间;备份/还原出现问题;失去太多服务器;在故障切换后的意外配置等等。
|
||||
|
||||
我们需要确保协调器正确地连接服务器。这是竞争性写入负载有用的地方:如果设置不正确,复制很容易中断。我们会得到 `DUPLICATE KEY` 或其他错误提示出错。
|
||||
|
||||
这是特别重要的,因此我们改进协调器并引入新的行为,以允许我们在安全的环境中测试这些变化。
|
||||
|
||||
#### 出现:混乱测试
|
||||
|
||||
上面所示的测试程序将捕获(并已经捕获)我们基础设施许多部分的问题。这些够了吗?
|
||||
|
||||
在生产环境中总是有其他的东西。有些特定测试方法不适用于我们的生产集群。它们不具有相同的流量和流量方式,也不具有完全相同的服务器集。故障类型可能有所不同。
|
||||
|
||||
我们正在为我们的生产集群设计混乱测试。 混乱测试将会在我们的生产中,但是按照预期的时间表和充分控制的方式来逐个破坏我们的部分生产环境。 混乱测试在恢复机制中引入更高层次的信赖,并影响(因此测试)我们的基础设施和应用程序的更大部分。
|
||||
|
||||
这是微妙的工作:当我们承认需要混乱测试时,我们也希望可以避免对我们的服务造成不必要的影响。不同的测试将在风险级别和影响方面有所不同,我们将努力确保我们的服务的可用性。
|
||||
|
||||
### 模式迁移
|
||||
|
||||
[我们使用 gh-ost][43]来运行实时<ruby>模式迁移<rt>schema migration</rt></ruby>。gh-ost 是稳定的,但也处于活跃开发中,重大新功能正在不断开发和计划中。
|
||||
|
||||
gh-ost 通过将数据复制到 ghost 表来迁移,将由二进制日志拦截的进一步更改应用到 ghost 表中,就如其正在写入原始表。然后它将 ghost 表交换代替原始表。迁移完成时,GitHub 继续使用由 gh-ost 生成和填充的表。
|
||||
|
||||
在这个时候,几乎所有的 GitHub 的 MySQL 数据都被 gh-ost 重新创建,其中大部分重新创建多次。我们必须高度信赖 gh-ost,让它一遍遍地操弄我们的数据,即使它还处于活跃开发中。下面是我们如何获得这种信赖的。
|
||||
|
||||
gh-ost 提供生产环境测试能力。它支持在副本上运行迁移,其方式与在主服务器上运行的方式大致相同: gh-ost 将连接到副本,并将其视为主服务器。它将采用与实际主机迁移相同的方式解析其二进制日志。但是,它将复制行并将二进制日志事件应用于副本,并避免对主服务器进行写入。
|
||||
|
||||
我们在生产环境中给 gh-ost 提供专用的副本。这些副本并不为生产环境提供服务。每个这样的副本将检索生产表的当前列表,并以随机顺序对其进行迭代。一个接一个地选择一个表并在该表上执行复制迁移。迁移实际上并不修改表结构,而是运行一个微不足道的 `ENGINE=InnoDB` 更改。该测试运行迁移时,如果表正在生产环境中使用,会复制实际的生产数据,并接受二进制日志之外的真实的生产环境流量。
|
||||
|
||||
这些迁移可以被审计。以下是我们如何从对话中(chatops)检查运行中的测试状态:
|
||||
|
||||
**ggunson**
|
||||
|
||||
```
|
||||
.migration test-status
|
||||
```
|
||||
|
||||
**Hubot**
|
||||
|
||||
```
|
||||
# Migrating `prod`.`pull_requests`; Ghost table is `prod`.`_pull_requests_gho`
|
||||
# Migrating ghost-db-mysql-0007:3306; inspecting ghost-db-mysql-0007:3306; executing on ghost-db-mysql-0007
|
||||
# Migration started at Mon Jan 30 02:13:39 -0800 2017
|
||||
# chunk-size: 2500; max-lag-millis: 1500ms; max-load: Threads_running=30; critical-load: Threads_running=1000; nice-ratio: 0.000000
|
||||
# throttle-additional-flag-file: /tmp/gh-ost.throttle
|
||||
# panic-flag-file: /tmp/ghost-test-panic.flag
|
||||
# Serving on unix socket: /tmp/gh-ost.test.sock
|
||||
Copy: 57992500/86684838 66.9%; Applied: 57708; Backlog: 1/100; Time: 3h28m38s(total), 3h28m36s(copy); streamer: mysql-bin.000576:142993938; State: migrating; ETA: 1h43m12s
|
||||
```
|
||||
|
||||
当测试迁移完成表数据的复制时,它将停止复制并执行切换,使用 ghost 表替换原始表,然后交换回来。我们对实际替换数据并不感兴趣。相反,我们将留下原始的表和 ghost 表,它们应该是相同的。我们通过校验两个表的整个表数据来验证。
|
||||
|
||||
测试能以下列方式完成:
|
||||
|
||||
* _成功_ :一切顺利,校验和相同。我们期待看到这一结果。
|
||||
* _失败_ :执行问题。这可能偶尔发生,因为迁移进程被杀死、复制问题等,并且通常与 gh-ost 自身无关。
|
||||
* _校验失败_ :表数据不一致。对于被测试的分支,这个需要修复。对于正在进行的 master 分支测试,这意味着立即阻止生产迁移。我们不会遇到后者。
|
||||
|
||||
测试结果经过审核,发送到机器人聊天室,作为事件发送到我们的度量系统。下图中的每条垂直线代表成功的迁移测试:
|
||||
|
||||

|
||||
|
||||
这些测试不断运行。如果发生故障,我们会收到通知。当然,我们可以随时访问机器人聊天室(chatops),了解发生了什么。
|
||||
|
||||
#### 测试新版本
|
||||
|
||||
我们不断改进 gh-ost。我们的开发流程基于 git 分支,然后我们通过[拉取请求(PR)][44]来提供合并。
|
||||
|
||||
提交的 gh-ost 拉取请求(PR)通过持续集成(CI)进行基本的编译和单元测试。一旦通过,该 PR 在技术上就有资格合并,但更好的是它[有资格通过 Heaven 进行部署][45]。作为我们基础架构中的敏感组件,在其进入 master 分支前,我们会小心部署分支进行密集测试。
|
||||
|
||||
**shlomi-noach**
|
||||
|
||||
```
|
||||
.deploy gh-ost/fix-reappearing-throttled-reasons to prod/ghost-db-mysql-0007
|
||||
```
|
||||
|
||||
**Hubot**
|
||||
```
|
||||
@shlomi-noach is deploying gh-ost/fix-reappearing-throttled-reasons (baee4f6) to production (ghost-db-mysql-0007).
|
||||
@shlomi-noach's production deployment of gh-ost/fix-reappearing-throttled-reasons (baee4f6) is done! (2s)
|
||||
@shlomi-noach, make sure you watch for exceptions in haystack
|
||||
```
|
||||
**jonahberquist**
|
||||
```
|
||||
.deploy gh-ost/interactive-command-question to prod/ghost-db-mysql-0012
|
||||
```
|
||||
**Hubot**
|
||||
```
|
||||
@jonahberquist is deploying gh-ost/interactive-command-question (be1ab17) to production (ghost-db-mysql-0012).
|
||||
@jonahberquist's production deployment of gh-ost/interactive-command-question (be1ab17) is done! (2s)
|
||||
@jonahberquist, make sure you watch for exceptions in haystack
|
||||
```
|
||||
**shlomi-noach**
|
||||
```
|
||||
.wcid gh-ost
|
||||
```
|
||||
**Hubot**
|
||||
```
|
||||
shlomi-noach testing fix-reappearing-throttled-reasons 41 seconds ago: ghost-db-mysql-0007
|
||||
jonahberquist testing interactive-command-question 7 seconds ago: ghost-db-mysql-0012
|
||||
|
||||
Nobody is in the queue.
|
||||
```
|
||||
|
||||
一些 PR 很小,不影响数据本身。对状态消息,交互式命令等的更改对 gh-ost 应用程序的影响较小。而其他的 PR 对迁移逻辑和操作会造成重大变化,我们将严格测试这些,通过我们的生产表车队运行这些,直到其满足了这些改变不会造成数据损坏威胁的程度。
|
||||
|
||||
### 总结
|
||||
|
||||
在整个测试过程中,我们建立对我们的系统的信赖。通过自动化这些测试,在生产环境中,我们得到了一切都按预期工作的反复确认。随着我们继续发展我们的基础设施,我们还通过调整测试来覆盖最新的变化。
|
||||
|
||||
产品总会有令你意想不到的未被测试覆盖的场景。我们对生产环境的测试越多,我们对应用程序的期望越多,基础设施的能力就越强。
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://githubengineering.com/mysql-testing-automation-at-github/
|
||||
|
||||
作者:[tomkrouper][a], [Shlomi Noach][b]
|
||||
译者:[MonkeyDEcho](https://github.com/MonkeyDEcho)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://github.com/tomkrouper
|
||||
[b]:https://github.com/shlomi-noach
|
||||
[1]:https://github.com/tomkrouper
|
||||
[2]:https://github.com/jessbreckenridge
|
||||
[3]:https://github.com/jessbreckenridge
|
||||
[4]:https://github.com/jessbreckenridge
|
||||
[5]:https://github.com/jessbreckenridge
|
||||
[6]:https://github.com/jessbreckenridge
|
||||
[7]:https://github.com/jessbreckenridge
|
||||
[8]:https://github.com/jessbreckenridge
|
||||
[9]:https://github.com/jessbreckenridge
|
||||
[10]:https://github.com/jessbreckenridge
|
||||
[11]:https://github.com/jessbreckenridge
|
||||
[12]:https://github.com/jessbreckenridge
|
||||
[13]:https://github.com/jessbreckenridge
|
||||
[14]:https://github.com/jessbreckenridge
|
||||
[15]:https://github.com/jessbreckenridge
|
||||
[16]:https://github.com/jessbreckenridge
|
||||
[17]:https://github.com/jessbreckenridge
|
||||
[18]:https://github.com/jessbreckenridge
|
||||
[19]:https://github.com/jessbreckenridge
|
||||
[20]:https://github.com/jessbreckenridge
|
||||
[21]:https://github.com/jessbreckenridge
|
||||
[22]:https://github.com/jessbreckenridge
|
||||
[23]:https://github.com/jessbreckenridge
|
||||
[24]:https://github.com/jessbreckenridge
|
||||
[25]:https://github.com/shlomi-noach
|
||||
[26]:https://github.com/shlomi-noach
|
||||
[27]:https://github.com/shlomi-noach
|
||||
[28]:https://github.com/jonahberquist
|
||||
[29]:https://github.com/jonahberquist
|
||||
[30]:https://github.com/jonahberquist
|
||||
[31]:https://githubengineering.com/mysql-testing-automation-at-github/
|
||||
[32]:https://github.com/tomkrouper
|
||||
[33]:https://github.com/tomkrouper
|
||||
[34]:https://github.com/shlomi-noach
|
||||
[35]:https://github.com/shlomi-noach
|
||||
[36]:https://githubengineering.com/mysql-testing-automation-at-github/#backups
|
||||
[37]:https://www.percona.com/software/mysql-database/percona-xtrabackup
|
||||
[38]:https://dev.mysql.com/doc/refman/5.6/en/replication-delayed.html
|
||||
[39]:https://githubengineering.com/mysql-testing-automation-at-github/#failovers
|
||||
[40]:http://githubengineering.com/orchestrator-github/
|
||||
[41]:https://githubengineering.com/context-aware-mysql-pools-via-haproxy/
|
||||
[42]:https://githubengineering.com/mysql-testing-automation-at-github/#schema-migrations
|
||||
[43]:http://githubengineering.com/gh-ost-github-s-online-migration-tool-for-mysql/
|
||||
[44]:https://github.com/github/gh-ost/pulls
|
||||
[45]:https://githubengineering.com/deploying-branches-to-github-com/
|
||||
[46]:https://githubengineering.com/mysql-testing-automation-at-github/#summary
|
||||
@@ -0,0 +1,80 @@
|
||||
混合云的变化
|
||||
============================================================
|
||||
|
||||
> 围绕云计算的概念和术语仍然很新,但是也在不断的改进。
|
||||
|
||||
|
||||

|
||||
|
||||
不管怎么看,云计算也只有十多年的发展时间。一些我们习以为常的云计算的概念和术语仍然很新。美国国家标准与技术研究所(NIST)文档显示,一些已经被熟悉的术语定义在 2011 年才被发布,例如基础设施即服务(IaaS),而在此之前它就以草案的形式广泛流传。
|
||||
|
||||
在该文档中其它定义中,有一个叫做<ruby>混合云<rt>hybrid cloud</rt></ruby>。让我们回溯一下该术语在这段期间的变化是很有启发性的。云基础设施已经超越了相对简单的分类。此外,它还强调了开源软件的使用者所熟悉的优先级,例如灵活性、可移植性、选择性,已经被运用到了混合云上。
|
||||
|
||||
NIST 对混合云最初的定义主要集中于<ruby>云爆发<rt>cloud bursting</rt></ruby>,你能使用内部的基础设施去处理一个基本的计算负荷,但是如果你的负荷量暴涨,可以将多出来的转为使用公有云。与之密切联系的是加强私有云与公有云之间 API 的兼容性,甚至是创造一个现货市场来提供最便宜的容量。
|
||||
|
||||
Nick Carr 在 [The Big Switch][10] 一书中提出一个概念,云是一种计算单元,其与输电网类似。这个故事不错,但是即使在早期,[这种类比的局限性也变得很明显][11]。计算不是以电流方式呈现的一种物品。需要关注的是,公有云提供商以及 OpenStack 一类的开源云软件激增的新功能,可见许多用户并不仅仅是寻找最便宜的通用计算能力。
|
||||
|
||||
云爆发的概念基本上忽略了计算是与数据相联系的现实,你不可能只移动洪水般突如其来的数据而不承担巨大的带宽费用,以及不用为转移需要花费的时间而操作。Dave McCrory 发明了 “<ruby>数据引力<rt>data gravity</rt></ruby>”一词去描述这个限制。
|
||||
|
||||
那么既然混合云有如此负面的情况,为什么我们现在还要再讨论混合云?
|
||||
|
||||
正如我说的,混合云的最初的构想是在云爆发的背景下诞生的。云爆发强调的是快速甚至是即时的将工作环境从一个云转移到另一个云上;然而,混合云也意味着应用和数据的移植性。确实,如之前 [2011 年我在 CNET 的文章][12]中写到:“我认为过度关注于全自动的工作转换给我们自己造成了困扰,我们真正应该关心的是,如果供应商不能满意我们的需求或者尝试将我们锁定在其平台上时,我们是否有将数据从一个地方到另一个地方的迁移能力。”
|
||||
|
||||
从那以后,探索云之间的移植性有了进一步的进展。
|
||||
|
||||
Linux 是云移植性的关键,因为它能运行在各种地方,无论是从裸机到内部虚拟基础设施,还是从私有云到公有云。Linux 提供了一个完整、可靠的平台,其具有稳定的 API 接口,且可以依靠这些接口编写程序。
|
||||
|
||||
被广泛采纳的容器进一步加强了 Linux 提供应用在云之间移植的能力。通过提供一个包含了应用的基础配置环境的镜像,应用在开发、测试和最终运行环境之间移动时容器提供了可移植性和兼容性。
|
||||
|
||||
Linux 容器被应用到要求可移植性、可配置性以及独立性的许多方面上。不管是预置的云,还是公有云,以及混合云都是如此。
|
||||
|
||||
容器使用的是基于镜像的部署模式,这让在不同环境中分享一个应用或者具有全部基础环境的服务集变得容易了。
|
||||
|
||||
在 OCI 支持下开发的规范定义了容器镜像的内容及其所依赖、环境、参数和一些镜像正确运行所必须的要求。在标准化的作用下,OCI 为许多其它工具提供了一个机会,它们现在可以依靠稳定的运行环境和镜像规范了。
|
||||
|
||||
同时,通过 Gluster 和 Ceph 这类的开源技术,分布式存储能提供数据在云上的可移植性。 物理约束限制了如何快速简单地把数据从一个地方移动到另一个地方;然而,随着组织部署和使用不同类型的基础架构,他们越来越渴望一个不受物理、虚拟和云资源限制的开放的软件定义储存平台。
|
||||
|
||||
尤其是在数据存储需求飞速增长的情况下,由于预测分析,物联网和实时监控的趋势。[2016 年的一项研究表明][13],98% 的 IT 决策者认为一个更敏捷的存储解决方案对他们的组织是有利的。在同一个研究中,他们列举出不恰当的存储基础设施是最令他们组织受挫的事情之一。
|
||||
|
||||
混合云表现出的是提供在不同计算能力和资源之间合适的移植性和兼容性。其不仅仅是将私有云和公有云同时运用在一个应用中。它是一套多种类型的服务,其中的一部分可能是你们 IT 部门建立和操作的,而另一部分可能来源于外部。
|
||||
|
||||
它们可能是软件即服务(SaaS)应用的混合,例如邮件和客户关系管理(CRM)。被 Kubernetes 这类开源软件协调在一起的容器平台越来越受新开发应用的欢迎。你的组织可能正在运用某一家大型云服务提供商来做一些事情。同时你也能在私有云或更加传统的内部基础设施上操作一些你自己的基础设施。
|
||||
|
||||
这就是现在混合云的现状,它能被归纳为两个选择,选择最合适的基础设施和服务,以及选择把应用和数据从一个地方移动到另一个你想的地方。
|
||||
|
||||
**相关阅读: [多重云和混合云有什么不同?][6]**
|
||||
|
||||
(题图 : [Flickr 使用者: theaucitron][9] (CC BY-SA 2.0))
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
Gordon Haff 是红帽云的布道者,常受到业内和客户的高度赞赏,帮助红帽云组合方案的发展。他是《Computing Next: How the Cloud Opens the Future》的作者,除此之外他还有许多出版物。在红帽之前,Gordon 写了大量的研究简报,经常被纽约时报等出版物在 IT 类话题上引用,在产品和市场策略上给予客户建议。他职业生涯的早期,在 Data General 他负责将各种不同的计算机系统,从微型计算机到大型的 UNIX 服务器,引入市场。他有麻省理工学院和达特茅斯学院的工程学位,还是康奈尔大学约翰逊商学院的工商管理学硕士。
|
||||
|
||||
-----
|
||||
|
||||
via: https://opensource.com/article/17/7/hybrid-cloud
|
||||
|
||||
作者:[Gordon Haff (Red Hat)][a]
|
||||
译者:[ZH1122](https://github.com/ZH1122)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/ghaff
|
||||
[1]:https://opensource.com/resources/cloud?src=cloud_resource_menu1
|
||||
[2]:https://opensource.com/resources/what-is-openstack?src=cloud_resource_menu2
|
||||
[3]:https://opensource.com/resources/what-is-kubernetes?src=cloud_resource_menu3
|
||||
[4]:https://opensource.com/16/12/yearbook-why-operating-system-matters?src=cloud_resource_menu4
|
||||
[5]:https://opensource.com/business/16/10/interview-andy-cathrow-anchore?src=cloud_resource_menu5
|
||||
[6]:https://enterprisersproject.com/article/2017/7/multi-cloud-vs-hybrid-cloud-whats-difference
|
||||
[7]:https://opensource.com/article/17/7/hybrid-cloud?rate=ztmV2D_utD03cID1u41Al08w0XFm6rXXwCJdTwqI4iw
|
||||
[8]:https://opensource.com/user/21220/feed
|
||||
[9]:https://www.flickr.com/photos/theaucitron/5810163712/in/photolist-5p9nh3-6EkSKG-6EgGEF-9hYBcr-abCSpq-9zbjDz-4PVqwm-9RqBfq-abA2T4-4nXfwv-9RQkdN-dmjSdA-84o2ER-abA2Wp-ehyhPC-7oFYrc-4nvqBz-csMQXb-nRegFf-ntS23C-nXRyaB-6Xw3Mq-cRMaCq-b6wkkP-7u8sVQ-yqcg-6fTmk7-bzm3vU-6Xw3vL-6EkzCQ-d3W8PG-5MoveP-oMWsyY-jtMME6-XEMwS-2SeRXT-d2hjzJ-p2ZZVZ-7oFYoX-84r6Mo-cCizvm-gnnsg5-77YfPx-iDjqK-8gszbW-6MUZEZ-dhtwtk-gmpTob-6TBJ8p-mWQaAC/
|
||||
[10]:http://www.nicholascarr.com/?page_id=21
|
||||
[11]:https://www.cnet.com/news/there-is-no-big-switch-for-cloud-computing/
|
||||
[12]:https://www.cnet.com/news/cloudbursting-or-just-portable-clouds/
|
||||
[13]:https://www.redhat.com/en/technologies/storage/vansonbourne
|
||||
[14]:https://opensource.com/users/ghaff
|
||||
[15]:https://opensource.com/users/ghaff
|
||||
@@ -0,0 +1,293 @@
|
||||
用 C 语言对 Gtk+ 应用进行功能测试
|
||||
========
|
||||
|
||||
> 这个简单教程教你如何测试你应用的功能
|
||||
|
||||

|
||||
|
||||
自动化测试用来保证你程序的质量以及让它以预想的运行。单元测试只是检测你算法的某一部分,而并不注重各组件间的适应性。这就是为什么会有功能测试,它有时也称为集成测试。
|
||||
|
||||
功能测试简单地与你的用户界面进行交互,无论它是网站还是桌面应用。为了展示功能测试如何工作,我们以测试一个 Gtk+ 应用为例。为了简单起见,这个教程里,我们使用 Gtk+ 2.0 教程的示例。
|
||||
|
||||
### 基础设置
|
||||
|
||||
对于每一个功能测试,你通常需要定义一些全局变量,比如 “用户交互时延” 或者 “失败的超时时间”(也就是说,如果在指定的时间内一个事件没有发生,程序就要中断)。
|
||||
|
||||
```
|
||||
#define TTT_FUNCTIONAL_TEST_UTIL_IDLE_CONDITION(f) ((TttFunctionalTestUtilIdleCondition)(f))
|
||||
#define TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME (125000)
|
||||
#define TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME_LONG (500000)
|
||||
typedef gboolean (*TttFunctionalTestUtilIdleCondition)(gpointer data);
|
||||
struct timespec ttt_functional_test_util_default_timeout = {
|
||||
20,
|
||||
0,
|
||||
};
|
||||
```
|
||||
|
||||
现在我们可以实现我们自己的超时函数。这里,为了能够得到期望的延迟,我们采用 `usleep` 函数。
|
||||
|
||||
```
|
||||
void
|
||||
ttt_functional_test_util_reaction_time()
|
||||
{
|
||||
usleep(TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME);
|
||||
}
|
||||
|
||||
void
|
||||
ttt_functional_test_util_reaction_time_long()
|
||||
{
|
||||
usleep(TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME_LONG);
|
||||
}
|
||||
```
|
||||
|
||||
直到获得控制状态,超时函数才会推迟执行。这对于一个异步执行的动作很有帮助,这也是为什么采用这么长的时延。
|
||||
|
||||
```
|
||||
void
|
||||
ttt_functional_test_util_idle_condition_and_timeout(
|
||||
TttFunctionalTestUtilIdleCondition idle_condition,
|
||||
struct timespec *timeout,
|
||||
pointer data)
|
||||
{
|
||||
struct timespec start_time, current_time;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC,
|
||||
&start_time);
|
||||
|
||||
while(TTT_FUNCTIONAL_TEST_UTIL_IDLE_CONDITION(idle_condition)(data)){
|
||||
ttt_functional_test_util_reaction_time();
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC,
|
||||
¤t_time);
|
||||
|
||||
if(start_time.tv_sec + timeout->tv_sec < current_time.tv_sec){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ttt_functional_test_util_reaction_time();
|
||||
}
|
||||
```
|
||||
|
||||
### 与图形化用户界面交互
|
||||
|
||||
为了模拟用户交互的操作, [Gdk 库][7] 为我们提供了一些需要的函数。要完成我们的工作,我们只需要如下 3 个函数:
|
||||
|
||||
* `gdk_display_warp_pointer()`
|
||||
* `gdk_test_simulate_button()`
|
||||
* `gdk_test_simulate_key()`
|
||||
|
||||
|
||||
举个例子,为了测试按钮点击,我们可以这么做:
|
||||
|
||||
```
|
||||
gboolean
|
||||
ttt_functional_test_util_button_click(GtkButton *button)
|
||||
{
|
||||
GtkWidget *widget;
|
||||
|
||||
GdkWindow *window;
|
||||
|
||||
gint x, y;
|
||||
gint origin_x, origin_y;
|
||||
|
||||
if(button == NULL ||
|
||||
!GTK_IS_BUTTON(button)){
|
||||
return(FALSE);
|
||||
}
|
||||
|
||||
widget = button;
|
||||
|
||||
if(!GTK_WIDGET_REALIZED(widget)){
|
||||
ttt_functional_test_util_reaction_time_long();
|
||||
}
|
||||
|
||||
/* retrieve window and pointer position */
|
||||
gdk_threads_enter();
|
||||
|
||||
window = gtk_widget_get_window(widget);
|
||||
|
||||
x = widget->allocation.x + widget->allocation.width / 2.0;
|
||||
y = widget->allocation.y + widget->allocation.height / 2.0;
|
||||
|
||||
gdk_window_get_origin(window, &origin_x, &origin_y);
|
||||
|
||||
gdk_display_warp_pointer(gtk_widget_get_display(widget),
|
||||
gtk_widget_get_screen(widget),
|
||||
origin_x + x, origin_y + y);
|
||||
|
||||
gdk_threads_leave();
|
||||
|
||||
/* click the button */
|
||||
ttt_functional_test_util_reaction_time();
|
||||
|
||||
gdk_test_simulate_button(window,
|
||||
x,
|
||||
y,
|
||||
1,
|
||||
GDK_BUTTON1_MASK,
|
||||
GDK_BUTTON_PRESS);
|
||||
|
||||
ttt_functional_test_util_reaction_time();
|
||||
|
||||
gdk_test_simulate_button(window,
|
||||
x,
|
||||
y,
|
||||
1,
|
||||
GDK_BUTTON1_MASK,
|
||||
GDK_BUTTON_RELEASE);
|
||||
|
||||
ttt_functional_test_util_reaction_time();
|
||||
|
||||
ttt_functional_test_util_reaction_time_long();
|
||||
|
||||
return(TRUE);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
我们想要保证按钮处于激活状态,因此我们提供一个空闲条件函数:
|
||||
|
||||
```
|
||||
gboolean
|
||||
ttt_functional_test_util_idle_test_toggle_active(
|
||||
GtkToggleButton **toggle_button)
|
||||
{
|
||||
gboolean do_idle;
|
||||
|
||||
do_idle = TRUE;
|
||||
|
||||
gdk_threads_enter();
|
||||
|
||||
if(*toggle_button != NULL &&
|
||||
GTK_IS_TOGGLE_BUTTON(*toggle_button) &&
|
||||
gtk_toggle_button_get_active(*toggle_button)){
|
||||
do_idle = FALSE;
|
||||
}
|
||||
|
||||
gdk_threads_leave();
|
||||
|
||||
return(do_idle);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 测试场景
|
||||
|
||||
因为这个 Tictactoe 程序非常简单,我们只需要确保点击了一个 [**GtkToggleButton**][8] 按钮即可。一旦该按钮肯定进入了激活状态,功能测试就可以执行。为了点击按钮,我们使用上面提到的很方便的 `util` 函数。
|
||||
|
||||
如图所示,我们假设,填满第一行,玩家 A 就赢,因为玩家 B 没有注意,只填充了第二行。
|
||||
|
||||
```
|
||||
GtkWindow *window;
|
||||
Tictactoe *ttt;
|
||||
|
||||
void*
|
||||
ttt_functional_test_gtk_main(void *)
|
||||
{
|
||||
gtk_main();
|
||||
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void
|
||||
ttt_functional_test_dumb_player_b()
|
||||
{
|
||||
GtkButton *buttons[3][3];
|
||||
|
||||
guint i;
|
||||
|
||||
/* to avoid race-conditions copy the buttons */
|
||||
gdk_threads_enter();
|
||||
|
||||
memcpy(buttons, ttt->buttons, 9 * sizeof(GtkButton *));
|
||||
|
||||
gdk_threads_leave();
|
||||
|
||||
/* TEST 1 - the dumb player B */
|
||||
for(i = 0; i < 3; i++){
|
||||
/* assert player A clicks the button successfully */
|
||||
if(!ttt_functional_test_util_button_click(buttons[0][i])){
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
functional_test_util_idle_condition_and_timeout(
|
||||
ttt_functional_test_util_idle_test_toggle_active,
|
||||
ttt_functional_test_util_default_timeout,
|
||||
&buttons[0][i]);
|
||||
|
||||
/* assert player B clicks the button successfully */
|
||||
if(!ttt_functional_test_util_button_click(buttons[1][i])){
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
functional_test_util_idle_condition_and_timeout(
|
||||
ttt_functional_test_util_idle_test_toggle_active,
|
||||
ttt_functional_test_util_default_timeout,
|
||||
&buttons[1][i]);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
pthread_t thread;
|
||||
|
||||
gtk_init(&argc, &argv);
|
||||
|
||||
/* start the tictactoe application */
|
||||
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
|
||||
ttt = tictactoe_new();
|
||||
gtk_container_add(window, ttt);
|
||||
|
||||
gtk_widget_show_all(window);
|
||||
|
||||
/* start the Gtk+ dispatcher */
|
||||
pthread_create(&thread, NULL,
|
||||
ttt_functional_test_gtk_main, NULL);
|
||||
|
||||
/* launch test routines */
|
||||
ttt_functional_test_dumb_player_b();
|
||||
|
||||
/* terminate the application */
|
||||
gdk_threads_enter();
|
||||
|
||||
gtk_main_quit();
|
||||
|
||||
gdk_threads_leave();
|
||||
|
||||
return(0);
|
||||
}
|
||||
```
|
||||
|
||||
(题图:opensource.com)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
|
||||
Joël Krähemann - 精通 C 语言编程的自由软件爱好者。不管代码多复杂,它也是一点点写成的。作为高级的 Gtk+ 程序开发者,我知道多线程编程有多大的挑战性,有了多线程编程,我们就有了未来需求的良好基础。
|
||||
|
||||
----
|
||||
via: https://opensource.com/article/17/7/functional-testing
|
||||
|
||||
作者:[Joël Krähemann][a]
|
||||
译者:[sugarfillet](https://github.com/sugarfillet)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/joel2001k
|
||||
[1]:http://www.opengroup.org/onlinepubs/009695399/functions/memcpy.html
|
||||
[2]:http://www.opengroup.org/onlinepubs/009695399/functions/exit.html
|
||||
[3]:http://www.opengroup.org/onlinepubs/009695399/functions/exit.html
|
||||
[4]:https://opensource.com/article/17/7/functional-testing?rate=BxwmcarRgeZ4CW_9EkWQKKS2LxDGFqMMiUtt9z-ofhY
|
||||
[5]:https://opensource.com/user/127961/feed
|
||||
[6]:https://developer.gnome.org/gtk-tutorial/stable/
|
||||
[7]:https://developer.gnome.org/gdk3/stable/
|
||||
[8]:https://developer.gnome.org/gtk3/stable/GtkToggleButton.html
|
||||
[9]:https://opensource.com/users/joel2001k
|
||||
[10]:https://opensource.com/users/joel2001k
|
||||
[11]:https://opensource.com/article/17/7/functional-testing#comments
|
||||
@@ -0,0 +1,347 @@
|
||||
通过 SSH 实现 TCP / IP 隧道(端口转发):使用 OpenSSH 可能的 8 种场景
|
||||
============================================================
|
||||
|
||||
对于 [Secure Shell (SSH)][21] 这样的网络协议来说,其主要职责就是在终端模式下访问一个远程系统。因为 SSH 协议对传输数据进行了加密,所以通过它在远端系统执行命令是安全的。此外,我们还可以在这种加密后的连接上通过创建隧道(端口转发)的方式,来实现两个不同终端间的互联。凭借这种方式,只要我们能通过 SSH 创建连接,就可以绕开防火墙或者端口禁用的限制。
|
||||
|
||||
这个话题在网络领域有大量的应用和讨论:
|
||||
|
||||
* [Wikipedia: SSH Tunneling][12]
|
||||
* [O’Reilly: Using SSH Tunneling][13]
|
||||
* [Ssh.com: Tunneling Explained][14]
|
||||
* [Ssh.com: Port Forwarding][15]
|
||||
* [SecurityFocus: SSH Port Forwarding][16]
|
||||
* [Red Hat Magazine: SSH Port Forwarding][17]
|
||||
|
||||
我们在接下来的内容中并不讨论端口转发的细节,而是准备介绍一个如何使用 [OpenSSH][22] 来完成 TCP 端口转发的速查表,其中包含了八种常见的场景。有些 SSH 客户端,比如 [PuTTY][23],也允许通过界面配置的方式来实现端口转发。而我们着重关注的是通过 OpenSSH 来实现的的方式。
|
||||
|
||||
在下面的例子当中,我们假设环境中的网络划分为外部网络(network1)和内部网络(network2)两部分,并且这两个网络之间,只能在 externo1 与 interno1 之间通过 SSH 连接的方式来互相访问。外部网络的节点之间和内部网络的节点之间是完全联通的。
|
||||
|
||||

|
||||
|
||||
### 场景 1
|
||||
|
||||
> 在 externo1 节点访问由 interno1 节点提供的 TCP 服务(本地端口转发 / 绑定地址 = localhost / 主机 = localhost )
|
||||
|
||||
externo1 节点可以通过 OpenSSH 连接到 interno1 节点,之后我们想通过其访问运行在 5900 端口上的 VNC 服务。
|
||||
|
||||

|
||||
|
||||
我们可以通过下面的命令来实现:
|
||||
|
||||
```
|
||||
externo1 $ ssh -L 7900:localhost:5900 user@interno1
|
||||
```
|
||||
|
||||
现在,我们可以在 externo1 节点上确认下 7900 端口是否处于监听状态中:
|
||||
|
||||
```
|
||||
externo1 $ netstat -ltn
|
||||
Active Internet connections (only servers)
|
||||
Proto Recv-Q Send-Q Local Address Foreign Address State
|
||||
...
|
||||
Tcp 0 0 127.0.0.1:7900 0.0.0.0:* LISTEN
|
||||
...
|
||||
```
|
||||
|
||||
我们只需要在 externo1 节点上执行如下命令即可访问 internal 节点的 VNC 服务:
|
||||
|
||||
```
|
||||
externo1 $ vncviewer localhost::7900
|
||||
```
|
||||
|
||||
注意:在 [vncviewer 的 man 手册](http://www.realvnc.com/products/free/4.1/man/vncviewer.html)中并未提及这种修改端口号的方式。在 [About VNCViewer configuration of the output TCP port][18] 中可以看到。这也是 [the TightVNC vncviewer][19] 所介绍的的。
|
||||
|
||||
### 场景 2
|
||||
|
||||
> 在 externo2 节点上访问由 interno1 节点提供的 TCP 服务(本地端口转发 / 绑定地址 = 0.0.0.0 / 主机 = localhost)
|
||||
|
||||
这次的场景跟方案 1 的场景的类似,但是我们这次想从 externo2 节点来连接到 interno1 上的 VNC 服务:
|
||||
|
||||

|
||||
|
||||
正确的命令如下:
|
||||
|
||||
```
|
||||
externo1 $ ssh -L 0.0.0.0:7900:localhost:5900 user@interno1
|
||||
```
|
||||
|
||||
看起来跟方案 1 中的命令类似,但是让我们看看 `netstat` 命令的输出上的区别。7900 端口被绑定到了本地(`127.0.0.1`),所以只有本地进程可以访问。这次我们将端口关联到了 `0.0.0.0`,所以系统允许任何 IP 地址的机器访问 7900 这个端口。
|
||||
|
||||
```
|
||||
externo1 $ netstat -ltn
|
||||
Active Internet connections (only servers)
|
||||
Proto Recv-Q Send-Q Local Address Foreign Address State
|
||||
...
|
||||
Tcp 0 0 0.0.0.0:7900 0.0.0.0:* LISTEN
|
||||
...
|
||||
```
|
||||
|
||||
所以现在在 externo2 节点上,我们可以执行:
|
||||
|
||||
```
|
||||
externo2 $ vncviewer externo1::7900
|
||||
```
|
||||
|
||||
来连接到 interno1 节点上的 VNC 服务。
|
||||
|
||||
除了将 IP 指定为 `0.0.0.0` 之外,我们还可以使用参数 `-g`(允许远程机器使用本地端口转发),完整命令如下:
|
||||
|
||||
```
|
||||
externo1 $ ssh -g -L 7900:localhost:5900 user@interno1
|
||||
```
|
||||
|
||||
这条命令与前面的命令能实现相同效果:
|
||||
|
||||
```
|
||||
externo1 $ ssh -L 0.0.0.0:7900:localhost:5900 user@interno1
|
||||
```
|
||||
|
||||
换句话说,如果我们想限制只能连接到系统上的某个 IP,可以像下面这样定义:
|
||||
|
||||
```
|
||||
externo1 $ ssh -L 192.168.24.80:7900:localhost:5900 user@interno1
|
||||
|
||||
externo1 $ netstat -ltn
|
||||
Active Internet connections (only servers)
|
||||
Proto Recv-Q Send-Q Local Address Foreign Address State
|
||||
...
|
||||
Tcp 0 0 192.168.24.80:7900 0.0.0.0:* LISTEN
|
||||
...
|
||||
```
|
||||
|
||||
### 场景 3
|
||||
|
||||
> 在 interno1 上访问由 externo1 提供的 TCP 服务(远程端口转发 / 绑定地址 = localhost / 主机 = localhost)
|
||||
|
||||
在场景 1 中 SSH 服务器与 TCP 服务(VNC)提供者在同一个节点上。现在我们想在 SSH 客户端所在的节点上,提供一个 TCP 服务(VNC)供 SSH 服务端来访问:
|
||||
|
||||

|
||||
|
||||
将方案 1 中的命令参数由 `-L` 替换为 `-R`。
|
||||
|
||||
完整命令如下:
|
||||
|
||||
```
|
||||
externo1 $ ssh -R 7900:localhost:5900 user@interno1
|
||||
```
|
||||
|
||||
然后我们就能看到 interno1 节点上对 7900 端口正在监听:
|
||||
|
||||
```
|
||||
interno1 $ netstat -lnt
|
||||
Active Internet connections (only servers)
|
||||
Proto Recv-Q Send-Q Local Address Foreign Address State
|
||||
...
|
||||
Tcp 0 0 127.0.0.1:7900 0.0.0.0:* LISTEN
|
||||
...
|
||||
```
|
||||
|
||||
现在在 interno1 节点上,我们可以使用如下命令来访问 externo1 上的 VNC 服务:
|
||||
|
||||
```
|
||||
interno1 $ vncviewer localhost::7900
|
||||
```
|
||||
|
||||
### 场景 4
|
||||
|
||||
> interno2 使用 externo1 上提供的 TCP 服务(远端端口转发 / 绑定地址 = 0.0.0.0 / 主机 = localhost)
|
||||
|
||||
与场景 3 类似,但是现在我们尝试指定允许访问转发端口的 IP(就像场景 2 中做的一样)为 `0.0.0.0`,这样其他节点也可以访问 VNC 服务:
|
||||
|
||||

|
||||
|
||||
正确的命令是:
|
||||
|
||||
```
|
||||
externo1 $ ssh -R 0.0.0.0:7900:localhost:5900 user@interno1
|
||||
```
|
||||
|
||||
但是这里有个重点需要了解,出于安全的原因,如果我们直接执行该命令的话可能不会生效,因为我们需要修改 SSH 服务端的一个参数值 `GatewayPorts`,它的默认值是:`no`。
|
||||
|
||||
> GatewayPorts
|
||||
>
|
||||
> 该参数指定了远程主机是否允许客户端访问转发端口。默认情况下,sshd(8) 只允许本机进程访问转发端口。这是为了阻止其他主机连接到该转发端口。GatewayPorts 参数可用于让 sshd 允许远程转发端口绑定到非回环地址上,从而可以让远程主机访问。当参数值设置为 “no” 的时候只有本机可以访问转发端口;“yes” 则表示允许远程转发端口绑定到通配地址上;或者设置为 “clientspecified” 则表示由客户端来选择哪些主机地址允许访问转发端口。默认值是 “no”。
|
||||
|
||||
如果我们没有修改服务器配置的权限,我们将不能使用该方案来进行端口转发。这是因为如果没有其他的限制,用户可以开启一个端口(> 1024)来监听来自外部的请求并转发到 `localhost:7900`。
|
||||
|
||||
参照这个案例:[netcat][24] ( [Debian # 310431: sshd_config should warn about the GatewayPorts workaround.][25] )
|
||||
|
||||
所以我们修改 `/etc/ssh/sshd_config`,添加如下内容:
|
||||
|
||||
```
|
||||
GatewayPorts clientspecified
|
||||
```
|
||||
|
||||
然后,我们使用如下命令来重载修改后的配置文件(在 Debian 和 Ubuntu 上)。
|
||||
|
||||
```
|
||||
sudo /etc/init.d/ssh reload
|
||||
```
|
||||
|
||||
我们确认一下现在 interno1 节点上存在 7900 端口的监听程序,监听来自不同 IP 的请求:
|
||||
|
||||
```
|
||||
interno1 $ netstat -ltn
|
||||
Active Internet connections (only servers)
|
||||
Proto Recv-Q Send-Q Local Address Foreign Address State
|
||||
...
|
||||
Tcp 0 0 0.0.0.0:7900 0.0.0.0:* LISTEN
|
||||
...
|
||||
```
|
||||
|
||||
然后我们就可以在 interno2 节点上使用 VNC 服务了:
|
||||
|
||||
```
|
||||
interno2 $ internal vncviewer1::7900
|
||||
```
|
||||
|
||||
### 场景 5
|
||||
|
||||
> 在 externo1 上使用由 interno2 提供的 TCP 服务(本地端口转发 / 绑定地址 localhost / 主机 = interno2 )
|
||||
|
||||

|
||||
|
||||
在这种场景下我们使用如下命令:
|
||||
|
||||
```
|
||||
externo1 $ ssh -L 7900:interno2:5900 user@interno1
|
||||
```
|
||||
|
||||
然后我们就能在 externo1 节点上,通过执行如下命令来使用 VNC 服务了:
|
||||
|
||||
```
|
||||
externo1 $ vncviewer localhost::7900
|
||||
```
|
||||
|
||||
### 场景 6
|
||||
|
||||
> 在 interno1 上使用由 externo2 提供的 TCP 服务(远程端口转发 / 绑定地址 = localhost / host = externo2)
|
||||
|
||||

|
||||
|
||||
在这种场景下,我们使用如下命令:
|
||||
|
||||
```
|
||||
externo1 $ ssh -R 7900:externo2:5900 user@interno1
|
||||
```
|
||||
|
||||
然后我们可以在 interno1 上通过执行如下命令来访问 VNC 服务:
|
||||
|
||||
```
|
||||
interno1 $ vncviewer localhost::7900
|
||||
```
|
||||
|
||||
### 场景7
|
||||
|
||||
> 在 externo2 上使用由 interno2 提供的 TCP 服务(本地端口转发 / 绑定地址 = 0.0.0.0 / 主机 = interno2)
|
||||
|
||||

|
||||
|
||||
本场景下,我们使用如下命令:
|
||||
|
||||
```
|
||||
externo1 $ ssh -L 0.0.0.0:7900:interno2:5900 user@interno1
|
||||
```
|
||||
|
||||
或者:
|
||||
|
||||
```
|
||||
externo1 $ ssh -g -L 7900:interno2:5900 user@interno1
|
||||
```
|
||||
|
||||
然后我们就可以在 externo2 上执行如下命令来访问 vnc 服务:
|
||||
|
||||
```
|
||||
externo2 $ vncviewer externo1::7900
|
||||
```
|
||||
|
||||
### 场景 8
|
||||
|
||||
> 在 interno2 上使用由 externo2 提供的 TCP 服务(远程端口转发 / 绑定地址 = 0.0.0.0 / 主机 = externo2)
|
||||
|
||||

|
||||
|
||||
本场景下我们使用如下命令:
|
||||
|
||||
```
|
||||
externo1 $ ssh -R 0.0.0.0:7900:externo2:5900 user@interno1
|
||||
```
|
||||
|
||||
SSH 服务器需要配置为:
|
||||
|
||||
```
|
||||
GatewayPorts clientspecified
|
||||
```
|
||||
|
||||
就像我们在场景 4 中讲过的那样。
|
||||
|
||||
然后我们可以在 interno2 节点上执行如下命令来访问 VNC 服务:
|
||||
|
||||
```
|
||||
interno2 $ internal vncviewer1::7900
|
||||
```
|
||||
|
||||
如果我们需要一次性的创建多个隧道,使用配置文件的方式替代一个可能很长的命令是一个更好的选择。假设我们只能通过 SSH 的方式访问某个特定网络,同时又需要创建多个隧道来访问该网络内不同服务器上的服务,比如 VNC 或者 [远程桌面][26]。此时只需要创建一个如下的配置文件 `$HOME/redirects` 即可(在 SOCKS 服务器 上)。
|
||||
|
||||
```
|
||||
# SOCKS server
|
||||
DynamicForward 1080
|
||||
|
||||
# SSH redirects
|
||||
LocalForward 2221 serverlinux1: 22
|
||||
LocalForward 2222 serverlinux2: 22
|
||||
LocalForward 2223 172.16.23.45:22
|
||||
LocalForward 2224 172.16.23.48:22
|
||||
|
||||
# RDP redirects for Windows systems
|
||||
LocalForward 3391 serverwindows1: 3389
|
||||
LocalForward 3392 serverwindows2: 3389
|
||||
|
||||
# VNC redirects for systems with "vncserver"
|
||||
LocalForward 5902 serverlinux1: 5901
|
||||
LocalForward 5903 172.16.23.45:5901
|
||||
```
|
||||
|
||||
然后我们只需要执行如下命令:
|
||||
|
||||
```
|
||||
externo1 $ ssh -F $HOME/redirects user@interno1
|
||||
```
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://wesharethis.com/2017/07/creating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh/
|
||||
|
||||
作者:[Ahmad][a]
|
||||
译者:[toutoudnf](https://github.com/toutoudnf)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://wesharethis.com/author/ahmad/
|
||||
[1]:https://wesharethis.com/2017/07/creating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh/#
|
||||
[2]:https://wesharethis.com/2017/07/creating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh/#Scenario_1_Use_onexterno1a_TCP_service_offered_byinterno1Local_port_forwarding_bind_address_localhost_host_localhost
|
||||
[3]:https://wesharethis.com/2017/07/creating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh/#Scenario_2_Use_onexterno2a_TCP_service_offered_byinterno1Local_port_forwarding_bind_address_0000_host_localhost
|
||||
[4]:https://wesharethis.com/2017/07/creating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh/#Scenario_3_Use_ininterno1a_TCP_service_offered_byexterno1Remote_port_forwarding_bind_address_localhost_host_localhost
|
||||
[5]:https://wesharethis.com/2017/07/creating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh/#Scenario_4_Use_ininterno2a_TCP_service_offered_byexterno1Remote_port_forwarding_bind_address_0000_host_localhost
|
||||
[6]:https://wesharethis.com/2017/07/creating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh/#Scenario_5_Use_inexterno1a_TCP_service_offered_byinterno2Local_port_forwarding_bind_address_localhost_host_interno2
|
||||
[7]:https://wesharethis.com/2017/07/creating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh/#Scenario_6_Use_ininterno1a_TCP_service_offered_byexterno2Remote_port_forwarding_bind_address_localhost_host_externo2
|
||||
[8]:https://wesharethis.com/2017/07/creating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh/#Scenario_7_Use_inexterno2a_TCP_service_offered_byinterno2Local_port_forwarding_bind_address_0000_host_interno2
|
||||
[9]:https://wesharethis.com/2017/07/creating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh/#Scenario_8_Use_ininterno2a_TCP_service_offered_byexterno2Remote_port_forwarding_bind_address_0000_host_externo2
|
||||
[10]:https://wesharethis.com/author/ahmad/
|
||||
[11]:https://wesharethis.com/2017/07/creating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh/#comments
|
||||
[12]:http://en.wikipedia.org/wiki/Tunneling_protocol#SSH_tunneling
|
||||
[13]:http://www.oreillynet.com/pub/a/wireless/2001/02/23/wep.html
|
||||
[14]:http://www.ssh.com/support/documentation/online/ssh/winhelp/32/Tunneling_Explained.html
|
||||
[15]:http://www.ssh.com/support/documentation/online/ssh/adminguide/32/Port_Forwarding.html
|
||||
[16]:http://www.securityfocus.com/infocus/1816
|
||||
[17]:http://magazine.redhat.com/2007/11/06/ssh-port-forwarding/
|
||||
[18]:http://www.realvnc.com/pipermail/vnc-list/2006-April/054551.html
|
||||
[19]:http://www.tightvnc.com/vncviewer.1.html
|
||||
[20]:https://bufferapp.com/add?url=https%3A%2F%2Fwesharethis.com%2F2017%2F07%2Fcreating-tcp-ip-port-forwarding-tunnels-ssh-8-possible-scenarios-using-openssh%2F&text=Creating%20TCP%20/%20IP%20(port%20forwarding)%20tunnels%20with%20SSH:%20The%208%20possible%20scenarios%20using%20OpenSSH
|
||||
[21]:http://en.wikipedia.org/wiki/Secure_Shell
|
||||
[22]:http://www.openssh.com/
|
||||
[23]:http://www.chiark.greenend.org.uk/~sgtatham/putty/
|
||||
[24]:http://en.wikipedia.org/wiki/Netcat
|
||||
[25]:http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=310431
|
||||
[26]:http://en.wikipedia.org/wiki/Remote_Desktop_Services
|
||||
@@ -0,0 +1,160 @@
|
||||
动态端口转发:安装带有 SSH 的 SOCKS 服务器
|
||||
=================
|
||||
|
||||
在上一篇文章([通过 SSH 实现 TCP / IP 隧道(端口转发):使用 OpenSSH 可能的 8 种场景][17])中,我们看到了处理端口转发的所有可能情况,不过那只是静态端口转发。也就是说,我们只介绍了通过 SSH 连接来访问另一个系统的端口的情况。
|
||||
|
||||
在那篇文章中,我们未涉及动态端口转发,此外一些读者没看过该文章,本篇文章中将尝试补充完整。
|
||||
|
||||
当我们谈论使用 SSH 进行动态端口转发时,我们说的是将 SSH 服务器转换为 [SOCKS][2] 服务器。那么什么是 SOCKS 服务器?
|
||||
|
||||
你知道 [Web 代理][3]是用来做什么的吗?答案可能是肯定的,因为很多公司都在使用它。它是一个直接连接到互联网的系统,允许没有互联网访问的[内部网][4]客户端让其浏览器通过代理来(尽管也有[透明代理][5])浏览网页。Web 代理除了允许输出到 Internet 之外,还可以缓存页面、图像等。已经由某客户端下载的资源,另一个客户端不必再下载它们。此外,它还可以过滤内容并监视用户的活动。当然了,它的基本功能是转发 HTTP 和 HTTPS 流量。
|
||||
|
||||
一个 SOCKS 服务器提供的服务类似于公司内部网络提供的代理服务器服务,但不限于 HTTP/HTTPS,它还允许转发任何 TCP/IP 流量(SOCKS 5 也支持 UDP)。
|
||||
|
||||
例如,假设我们希望在一个没有直接连接到互联网的内部网上通过 Thunderbird 使用 POP3 、 ICMP 和 SMTP 的邮件服务。如果我们只有一个 web 代理可以用,我们可以使用的唯一的简单方式是使用某个 webmail(也可以使用 [Thunderbird 的 Webmail 扩展][6])。我们还可以通过 [HTTP 隧道][7]来起到代理的用途。但最简单的方式是在网络中设置一个 SOCKS 服务器,它可以让我们使用 POP3、ICMP 和 SMTP,而不会造成任何的不便。
|
||||
|
||||
虽然有很多软件可以配置非常专业的 SOCKS 服务器,但用 OpenSSH 设置一个只需要简单的一条命令:
|
||||
|
||||
```
|
||||
Clientessh $ ssh -D 1080 user@servidorssh
|
||||
```
|
||||
|
||||
或者我们可以改进一下:
|
||||
|
||||
```
|
||||
Clientessh $ ssh -fN -D 0.0.0.0:1080 user@servidorssh
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
* 选项 `-D` 类似于选项为 `-L` 和 `-R` 的静态端口转发。像那些一样,我们可以让客户端只监听本地请求或从其他节点到达的请求,具体取决于我们将请求关联到哪个地址:
|
||||
```
|
||||
-D [bind_address:] port
|
||||
```
|
||||
|
||||
在静态端口转发中可以看到,我们使用选项 `-R` 进行反向端口转发,而动态转发是不可能的。我们只能在 SSH 客户端创建 SOCKS 服务器,而不能在 SSH 服务器端创建。
|
||||
* 1080 是 SOCKS 服务器的典型端口,正如 8080 是 Web 代理服务器的典型端口一样。
|
||||
* 选项 `-N` 防止实际启动远程 shell 交互式会话。当我们只用 `ssh` 来建立隧道时很有用。
|
||||
* 选项 `-f` 会使 `ssh` 停留在后台并将其与当前 shell 分离,以便使该进程成为守护进程。如果没有选项 `-N`(或不指定命令),则不起作用,否则交互式 shell 将与后台进程不兼容。
|
||||
|
||||
使用 [PuTTY][8] 也可以非常简单地进行端口重定向。与 `ssh -D 0.0.0.0:1080` 相当的配置如下:
|
||||
|
||||

|
||||
|
||||
对于通过 SOCKS 服务器访问另一个网络的应用程序,如果应用程序提供了对 SOCKS 服务器的特别支持,就会非常方便(虽然不是必需的),就像浏览器支持使用代理服务器一样。作为一个例子,如 Firefox 或 Internet Explorer 这样的浏览器使用 SOCKS 服务器访问另一个网络的应用程序:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
注意:上述截图来自 [IE for Linux][1] :如果您需要在 Linux 上使用 Internet Explorer,强烈推荐!
|
||||
|
||||
然而,最常见的浏览器并不要求 SOCKS 服务器,因为它们通常与代理服务器配合得更好。
|
||||
|
||||
不过,Thunderbird 也支持 SOCKS,而且很有用:
|
||||
|
||||

|
||||
|
||||
另一个例子:[Spotify][9] 客户端同样支持 SOCKS:
|
||||
|
||||

|
||||
|
||||
需要关注一下名称解析。有时我们会发现,在目前的网络中,我们无法解析 SOCKS 服务器另一端所要访问的系统的名称。SOCKS 5 还允许我们通过隧道传播 DNS 请求( 因为 SOCKS 5 允许我们使用 UDP)并将它们发送到另一端:可以指定是本地还是远程解析(或者也可以两者都试试)。支持此功能的应用程序也必须考虑到这一点。例如,Firefox 具有参数 `network.proxy.socks_remote_dns`(在 `about:config` 中),允许我们指定远程解析。而默认情况下,它在本地解析。
|
||||
|
||||
Thunderbird 也支持参数 `network.proxy.socks_remote_dns`,但由于没有地址栏来放置 `about:config`,我们需要改变它,就像在 [MozillaZine:about:config][10] 中读到的,依次点击 工具 → 选项 → 高级 → 常规 → 配置编辑器(按钮)。
|
||||
|
||||
没有对 SOCKS 特别支持的应用程序可以被 <ruby>sock 化<rt>socksified</rt></ruby>。这对于使用 TCP/IP 的许多应用程序都没有问题,但并不是全部。“sock 化” 需要加载一个额外的库,它可以检测对 TCP/IP 堆栈的请求,并修改请求,以通过 SOCKS 服务器重定向,从而不需要特别编程来支持 SOCKS 便可以正常通信。
|
||||
|
||||
在 Windows 和 [Linux][18] 上都有 “Sock 化工具”。
|
||||
|
||||
对于 Windows,我们举个例子,SocksCap 是一种闭源,但对非商业使用免费的产品,我使用了很长时间都十分满意。SocksCap 由一家名为 Permeo 的公司开发,该公司是创建 SOCKS 参考技术的公司。Permeo 被 [Blue Coat][11] 买下后,它[停止了 SocksCap 项目][12]。现在你仍然可以在互联网上找到 `sc32r240.exe` 文件。[FreeCap][13] 也是面向 Windows 的免费代码项目,外观和使用都非常类似于 SocksCap。然而,它工作起来更加糟糕,多年来一直没有缺失维护。看起来,它的作者倾向于推出需要付款的新产品 [WideCap][14]。
|
||||
|
||||
这是 SocksCap 的一个界面,可以看到我们 “sock 化” 了的几个应用程序。当我们从这里启动它们时,这些应用程序将通过 SOCKS 服务器访问网络:
|
||||
|
||||

|
||||
|
||||
在配置对话框中可以看到,如果选择了协议 SOCKS 5,我们可以选择在本地或远程解析名称:
|
||||
|
||||

|
||||
|
||||
在 Linux 上,如同往常一样,对某个远程命令我们都有许多替代方案。在 Debian/Ubuntu 中,命令行:
|
||||
|
||||
```
|
||||
$ Apt-cache search socks
|
||||
```
|
||||
|
||||
的输出会告诉我们很多。
|
||||
|
||||
最著名的是 [tsocks][15] 和 [proxychains][16]。它们的工作方式大致相同:只需用它们启动我们想要 “sock 化” 的应用程序就行。使用 `proxychains` 的 `wget` 的例子:
|
||||
|
||||
```
|
||||
$ Proxychains wget http://www.google.com
|
||||
ProxyChains-3.1 (http://proxychains.sf.net)
|
||||
--19: 13: 20-- http://www.google.com/
|
||||
Resolving www.google.com ...
|
||||
DNS-request | Www.google.com
|
||||
| S-chain | - <- - 10.23.37.3:1080-<><>-4.2.2.2:53-<><>-OK
|
||||
| DNS-response | Www.google.com is 72.14.221.147
|
||||
72.14.221.147
|
||||
Connecting to www.google.com | 72.14.221.147 |: 80 ...
|
||||
| S-chain | - <- - 10.23.37.3:1080-<><>-72.14.221.147:80-<><>-OK
|
||||
Connected.
|
||||
HTTP request sent, awaiting response ... 200 OK
|
||||
Length: unspecified [text / html]
|
||||
Saving to: `index.html '
|
||||
|
||||
[<=>] 6,016 24.0K / s in 0.2s
|
||||
|
||||
19:13:21 (24.0 KB / s) - `index.html 'saved [6016]
|
||||
```
|
||||
|
||||
要让它可以工作,我们必须在 `/etc/proxychains.conf` 中指定要使用的代理服务器:
|
||||
|
||||
```
|
||||
[ProxyList]
|
||||
Socks5 clientessh 1080
|
||||
```
|
||||
|
||||
我们也设置远程进行 DNS 请求:
|
||||
|
||||
```
|
||||
# Proxy DNS requests - no leak for DNS data
|
||||
Proxy_dns
|
||||
```
|
||||
|
||||
另外,在前面的输出中,我们已经看到了同一个 `proxychains` 的几条信息性的消息, 非 `wget` 的行是标有字符串 `|DNS-request|`、`|S-chain|` 或 `|DNS-response|` 的。如果我们不想看到它们,也可以在配置中进行调整:
|
||||
|
||||
```
|
||||
# Quiet mode (no output from library)
|
||||
Quiet_mode
|
||||
```
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://wesharethis.com/2017/07/15/dynamic-port-forwarding-mount-socks-server-ssh/
|
||||
|
||||
作者:[Ahmad][a]
|
||||
译者:[firmianay](https://github.com/firmianay)
|
||||
校对:[jasminepeng](https://github.com/jasminepeng)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://wesharethis.com/author/ahmad/
|
||||
[1]:https://wesharethis.com/goto/http://www.tatanka.com.br/ies4linux/page/Main_Page
|
||||
[2]:https://wesharethis.com/goto/http://en.wikipedia.org/wiki/SOCKS
|
||||
[3]:https://wesharethis.com/goto/http://en.wikipedia.org/wiki/Proxy_server
|
||||
[4]:https://wesharethis.com/goto/http://en.wikipedia.org/wiki/Intranet
|
||||
[5]:https://wesharethis.com/goto/http://en.wikipedia.org/wiki/Proxy_server#Transparent_and_non-transparent_proxy_server
|
||||
[6]:https://wesharethis.com/goto/http://webmail.mozdev.org/
|
||||
[7]:https://wesharethis.com/goto/http://en.wikipedia.org/wiki/HTTP_tunnel_(software)
|
||||
[8]:https://wesharethis.com/goto/http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html
|
||||
[9]:https://wesharethis.com/goto/https://www.spotify.com/int/download/linux/
|
||||
[10]:https://wesharethis.com/goto/http://kb.mozillazine.org/About:config
|
||||
[11]:https://wesharethis.com/goto/http://www.bluecoat.com/
|
||||
[12]:https://wesharethis.com/goto/http://www.bluecoat.com/products/sockscap
|
||||
[13]:https://wesharethis.com/goto/http://www.freecap.ru/eng/
|
||||
[14]:https://wesharethis.com/goto/http://widecap.ru/en/support/
|
||||
[15]:https://wesharethis.com/goto/http://tsocks.sourceforge.net/
|
||||
[16]:https://wesharethis.com/goto/http://proxychains.sourceforge.net/
|
||||
[17]:https://linux.cn/article-8945-1.html
|
||||
[18]:https://wesharethis.com/2017/07/10/linux-swap-partition/
|
||||
@@ -0,0 +1,292 @@
|
||||
开发一个 Linux 调试器(九):处理变量
|
||||
============================================================
|
||||
|
||||
变量是偷偷摸摸的。有时,它们会很高兴地呆在寄存器中,但是一转头就会跑到堆栈中。为了优化,编译器可能会完全将它们从窗口中抛出。无论变量在内存中的如何移动,我们都需要一些方法在调试器中跟踪和操作它们。这篇文章将会教你如何处理调试器中的变量,并使用 `libelfin` 演示一个简单的实现。
|
||||
|
||||
### 系列文章索引
|
||||
|
||||
1. [准备环境][1]
|
||||
2. [断点][2]
|
||||
3. [寄存器和内存][3]
|
||||
4. [ELF 和 DWARF][4]
|
||||
5. [源码和信号][5]
|
||||
6. [源码级逐步执行][6]
|
||||
7. [源码级断点][7]
|
||||
8. [堆栈展开][8]
|
||||
9. [处理变量][9]
|
||||
10. [高级话题][10]
|
||||
|
||||
在开始之前,请确保你使用的 `libelfin` 版本是[我分支上的 `fbreg`][11]。这包含了一些 hack 来支持获取当前堆栈帧的基址并评估位置列表,这些都不是由原生的 `libelfin` 提供的。你可能需要给 GCC 传递 `-gdwarf-2` 参数使其生成兼容的 DWARF 信息。但是在实现之前,我将详细说明 DWARF 5 最新规范中的位置编码方式。如果你想要了解更多信息,那么你可以从[这里][12]获取该标准。
|
||||
|
||||
### DWARF 位置
|
||||
|
||||
某一给定时刻的内存中变量的位置使用 `DW_AT_location` 属性编码在 DWARF 信息中。位置描述可以是单个位置描述、复合位置描述或位置列表。
|
||||
|
||||
* 简单位置描述:描述了对象的一个连续的部分(通常是所有部分)的位置。简单位置描述可以描述可寻址存储器或寄存器中的位置,或缺少位置(具有或不具有已知值)。比如,`DW_OP_fbreg -32`: 一个整个存储的变量 - 从堆栈帧基址开始的32个字节。
|
||||
* 复合位置描述:根据片段描述对象,每个对象可以包含在寄存器的一部分中或存储在与其他片段无关的存储器位置中。比如, `DW_OP_reg3 DW_OP_piece 4 DW_OP_reg10 DW_OP_piece 2`:前四个字节位于寄存器 3 中,后两个字节位于寄存器 10 中的一个变量。
|
||||
* 位置列表:描述了具有有限生存期或在生存期内更改位置的对象。比如:
|
||||
* `<loclist with 3 entries follows>`
|
||||
* `[ 0]<lowpc=0x2e00><highpc=0x2e19>DW_OP_reg0`
|
||||
* `[ 1]<lowpc=0x2e19><highpc=0x2e3f>DW_OP_reg3`
|
||||
* `[ 2]<lowpc=0x2ec4><highpc=0x2ec7>DW_OP_reg2`
|
||||
* 根据程序计数器的当前值,位置在寄存器之间移动的变量。
|
||||
|
||||
根据位置描述的种类,`DW_AT_location` 以三种不同的方式进行编码。`exprloc` 编码简单和复合的位置描述。它们由一个字节长度组成,后跟一个 DWARF 表达式或位置描述。`loclist` 和 `loclistptr` 的编码位置列表,它们在 `.debug_loclists` 部分中提供索引或偏移量,该部分描述了实际的位置列表。
|
||||
|
||||
### DWARF 表达式
|
||||
|
||||
使用 DWARF 表达式计算变量的实际位置。这包括操作堆栈值的一系列操作。有很多 DWARF 操作可用,所以我不会详细解释它们。相反,我会从每一个表达式中给出一些例子,给你一个可用的东西。另外,不要害怕这些;`libelfin` 将为我们处理所有这些复杂性。
|
||||
|
||||
* 字面编码
|
||||
* `DW_OP_lit0`、`DW_OP_lit1`……`DW_OP_lit31`
|
||||
* 将字面量压入堆栈
|
||||
* `DW_OP_addr <addr>`
|
||||
* 将地址操作数压入堆栈
|
||||
* `DW_OP_constu <unsigned>`
|
||||
* 将无符号值压入堆栈
|
||||
* 寄存器值
|
||||
* `DW_OP_fbreg <offset>`
|
||||
* 压入在堆栈帧基址找到的值,偏移给定值
|
||||
* `DW_OP_breg0`、`DW_OP_breg1`…… `DW_OP_breg31 <offset>`
|
||||
* 将给定寄存器的内容加上给定的偏移量压入堆栈
|
||||
* 堆栈操作
|
||||
* `DW_OP_dup`
|
||||
* 复制堆栈顶部的值
|
||||
* `DW_OP_deref`
|
||||
* 将堆栈顶部视为内存地址,并将其替换为该地址的内容
|
||||
* 算术和逻辑运算
|
||||
* `DW_OP_and`
|
||||
* 弹出堆栈顶部的两个值,并压回它们的逻辑 `AND`
|
||||
* `DW_OP_plus`
|
||||
* 与 `DW_OP_and` 相同,但是会添加值
|
||||
* 控制流操作
|
||||
* `DW_OP_le`、`DW_OP_eq`、`DW_OP_gt` 等
|
||||
* 弹出前两个值,比较它们,并且如果条件为真,则压入 `1`,否则为 `0`
|
||||
* `DW_OP_bra <offset>`
|
||||
* 条件分支:如果堆栈的顶部不是 `0`,则通过 `offset` 在表达式中向后或向后跳过
|
||||
* 输入转化
|
||||
* `DW_OP_convert <DIE offset>`
|
||||
* 将堆栈顶部的值转换为不同的类型,它由给定偏移量的 DWARF 信息条目描述
|
||||
* 特殊操作
|
||||
* `DW_OP_nop`
|
||||
* 什么都不做!
|
||||
|
||||
### DWARF 类型
|
||||
|
||||
DWARF 类型的表示需要足够强大来为调试器用户提供有用的变量表示。用户经常希望能够在应用程序级别进行调试,而不是在机器级别进行调试,并且他们需要了解他们的变量正在做什么。
|
||||
|
||||
DWARF 类型与大多数其他调试信息一起编码在 DIE 中。它们可以具有指示其名称、编码、大小、字节等的属性。无数的类型标签可用于表示指针、数组、结构体、typedef 以及 C 或 C++ 程序中可以看到的任何其他内容。
|
||||
|
||||
以这个简单的结构体为例:
|
||||
|
||||
```
|
||||
struct test{
|
||||
int i;
|
||||
float j;
|
||||
int k[42];
|
||||
test* next;
|
||||
};
|
||||
```
|
||||
|
||||
这个结构体的父 DIE 是这样的:
|
||||
|
||||
```
|
||||
< 1><0x0000002a> DW_TAG_structure_type
|
||||
DW_AT_name "test"
|
||||
DW_AT_byte_size 0x000000b8
|
||||
DW_AT_decl_file 0x00000001 test.cpp
|
||||
DW_AT_decl_line 0x00000001
|
||||
|
||||
```
|
||||
|
||||
上面说的是我们有一个叫做 `test` 的结构体,大小为 `0xb8`,在 `test.cpp` 的第 `1` 行声明。接下来有许多描述成员的子 DIE。
|
||||
|
||||
```
|
||||
< 2><0x00000032> DW_TAG_member
|
||||
DW_AT_name "i"
|
||||
DW_AT_type <0x00000063>
|
||||
DW_AT_decl_file 0x00000001 test.cpp
|
||||
DW_AT_decl_line 0x00000002
|
||||
DW_AT_data_member_location 0
|
||||
< 2><0x0000003e> DW_TAG_member
|
||||
DW_AT_name "j"
|
||||
DW_AT_type <0x0000006a>
|
||||
DW_AT_decl_file 0x00000001 test.cpp
|
||||
DW_AT_decl_line 0x00000003
|
||||
DW_AT_data_member_location 4
|
||||
< 2><0x0000004a> DW_TAG_member
|
||||
DW_AT_name "k"
|
||||
DW_AT_type <0x00000071>
|
||||
DW_AT_decl_file 0x00000001 test.cpp
|
||||
DW_AT_decl_line 0x00000004
|
||||
DW_AT_data_member_location 8
|
||||
< 2><0x00000056> DW_TAG_member
|
||||
DW_AT_name "next"
|
||||
DW_AT_type <0x00000084>
|
||||
DW_AT_decl_file 0x00000001 test.cpp
|
||||
DW_AT_decl_line 0x00000005
|
||||
DW_AT_data_member_location 176(as signed = -80)
|
||||
|
||||
```
|
||||
|
||||
每个成员都有一个名称、一个类型(它是一个 DIE 偏移量)、一个声明文件和行,以及一个指向其成员所在的结构体的字节偏移。其类型指向如下。
|
||||
|
||||
```
|
||||
< 1><0x00000063> DW_TAG_base_type
|
||||
DW_AT_name "int"
|
||||
DW_AT_encoding DW_ATE_signed
|
||||
DW_AT_byte_size 0x00000004
|
||||
< 1><0x0000006a> DW_TAG_base_type
|
||||
DW_AT_name "float"
|
||||
DW_AT_encoding DW_ATE_float
|
||||
DW_AT_byte_size 0x00000004
|
||||
< 1><0x00000071> DW_TAG_array_type
|
||||
DW_AT_type <0x00000063>
|
||||
< 2><0x00000076> DW_TAG_subrange_type
|
||||
DW_AT_type <0x0000007d>
|
||||
DW_AT_count 0x0000002a
|
||||
< 1><0x0000007d> DW_TAG_base_type
|
||||
DW_AT_name "sizetype"
|
||||
DW_AT_byte_size 0x00000008
|
||||
DW_AT_encoding DW_ATE_unsigned
|
||||
< 1><0x00000084> DW_TAG_pointer_type
|
||||
DW_AT_type <0x0000002a>
|
||||
|
||||
```
|
||||
|
||||
如你所见,我笔记本电脑上的 `int` 是一个 4 字节的有符号整数类型,`float`是一个 4 字节的浮点数。整数数组类型通过指向 `int` 类型作为其元素类型,`sizetype`(可以认为是 `size_t`)作为索引类型,它具有 `2a` 个元素。 `test *` 类型是 `DW_TAG_pointer_type`,它引用 `test` DIE。
|
||||
|
||||
### 实现简单的变量读取器
|
||||
|
||||
如上所述,`libelfin` 将为我们处理大部分复杂性。但是,它并没有实现用于表示可变位置的所有方法,并且在我们的代码中处理这些将变得非常复杂。因此,我现在选择只支持 `exprloc`。请根据需要添加对更多类型表达式的支持。如果你真的有勇气,请提交补丁到 `libelfin` 中来帮助完成必要的支持!
|
||||
|
||||
处理变量主要是将不同部分定位在存储器或寄存器中,读取或写入与之前一样。为了简单起见,我只会告诉你如何实现读取。
|
||||
|
||||
首先我们需要告诉 `libelfin` 如何从我们的进程中读取寄存器。我们创建一个继承自 `expr_context` 的类并使用 `ptrace` 来处理所有内容:
|
||||
|
||||
```
|
||||
class ptrace_expr_context : public dwarf::expr_context {
|
||||
public:
|
||||
ptrace_expr_context (pid_t pid) : m_pid{pid} {}
|
||||
|
||||
dwarf::taddr reg (unsigned regnum) override {
|
||||
return get_register_value_from_dwarf_register(m_pid, regnum);
|
||||
}
|
||||
|
||||
dwarf::taddr pc() override {
|
||||
struct user_regs_struct regs;
|
||||
ptrace(PTRACE_GETREGS, m_pid, nullptr, ®s);
|
||||
return regs.rip;
|
||||
}
|
||||
|
||||
dwarf::taddr deref_size (dwarf::taddr address, unsigned size) override {
|
||||
//TODO take into account size
|
||||
return ptrace(PTRACE_PEEKDATA, m_pid, address, nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
pid_t m_pid;
|
||||
};
|
||||
```
|
||||
|
||||
读取将由我们 `debugger` 类中的 `read_variables` 函数处理:
|
||||
|
||||
```
|
||||
void debugger::read_variables() {
|
||||
using namespace dwarf;
|
||||
|
||||
auto func = get_function_from_pc(get_pc());
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
我们上面做的第一件事是找到我们目前进入的函数,然后我们需要循环访问该函数中的条目来寻找变量:
|
||||
|
||||
```
|
||||
for (const auto& die : func) {
|
||||
if (die.tag == DW_TAG::variable) {
|
||||
//...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
我们通过查找 DIE 中的 `DW_AT_location` 条目获取位置信息:
|
||||
|
||||
```
|
||||
auto loc_val = die[DW_AT::location];
|
||||
```
|
||||
|
||||
接着我们确保它是一个 `exprloc`,并请求 `libelfin` 来评估我们的表达式:
|
||||
|
||||
```
|
||||
if (loc_val.get_type() == value::type::exprloc) {
|
||||
ptrace_expr_context context {m_pid};
|
||||
auto result = loc_val.as_exprloc().evaluate(&context);
|
||||
```
|
||||
|
||||
现在我们已经评估了表达式,我们需要读取变量的内容。它可以在内存或寄存器中,因此我们将处理这两种情况:
|
||||
|
||||
```
|
||||
switch (result.location_type) {
|
||||
case expr_result::type::address:
|
||||
{
|
||||
auto value = read_memory(result.value);
|
||||
std::cout << at_name(die) << " (0x" << std::hex << result.value << ") = "
|
||||
<< value << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
case expr_result::type::reg:
|
||||
{
|
||||
auto value = get_register_value_from_dwarf_register(m_pid, result.value);
|
||||
std::cout << at_name(die) << " (reg " << result.value << ") = "
|
||||
<< value << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw std::runtime_error{"Unhandled variable location"};
|
||||
}
|
||||
```
|
||||
|
||||
你可以看到,我根据变量的类型,打印输出了值而没有解释。希望通过这个代码,你可以看到如何支持编写变量,或者用给定的名字搜索变量。
|
||||
|
||||
最后我们可以将它添加到我们的命令解析器中:
|
||||
|
||||
```
|
||||
else if(is_prefix(command, "variables")) {
|
||||
read_variables();
|
||||
}
|
||||
```
|
||||
|
||||
### 测试一下
|
||||
|
||||
编写一些具有一些变量的小功能,不用优化并带有调试信息编译它,然后查看是否可以读取变量的值。尝试写入存储变量的内存地址,并查看程序改变的行为。
|
||||
|
||||
已经有九篇文章了,还剩最后一篇!下一次我会讨论一些你可能会感兴趣的更高级的概念。现在你可以在[这里][13]找到这个帖子的代码。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://blog.tartanllama.xyz/writing-a-linux-debugger-variables/
|
||||
|
||||
作者:[Simon Brand][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.twitter.com/TartanLlama
|
||||
[1]:https://linux.cn/article-8626-1.html
|
||||
[2]:https://linux.cn/article-8645-1.html
|
||||
[3]:https://linux.cn/article-8663-1.html
|
||||
[4]:https://linux.cn/article-8719-1.html
|
||||
[5]:https://linux.cn/article-8812-1.html
|
||||
[6]:https://linux.cn/article-8813-1.html
|
||||
[7]:https://linux.cn/article-8890-1.html
|
||||
[8]:https://linux.cn/article-8930-1.html
|
||||
[9]:https://blog.tartanllama.xyz/writing-a-linux-debugger-variables/
|
||||
[10]:https://blog.tartanllama.xyz/writing-a-linux-debugger-advanced-topics/
|
||||
[11]:https://github.com/TartanLlama/libelfin/tree/fbreg
|
||||
[12]:http://dwarfstd.org/
|
||||
[13]:https://github.com/TartanLlama/minidbg/tree/tut_variable
|
||||
@@ -0,0 +1,121 @@
|
||||
开发一个 Linux 调试器(十):高级主题
|
||||
============================================================
|
||||
|
||||
我们终于来到这个系列的最后一篇文章!这一次,我将对调试中的一些更高级的概念进行高层的概述:远程调试、共享库支持、表达式计算和多线程支持。这些想法实现起来比较复杂,所以我不会详细说明如何做,但是如果你有问题的话,我很乐意回答有关这些概念的问题。
|
||||
|
||||
### 系列索引
|
||||
|
||||
1. [准备环境][1]
|
||||
2. [断点][2]
|
||||
3. [寄存器和内存][3]
|
||||
4. [Elves 和 dwarves][4]
|
||||
5. [源码和信号][5]
|
||||
6. [源码层逐步执行][6]
|
||||
7. [源码层断点][7]
|
||||
8. [调用栈][8]
|
||||
9. [处理变量][9]
|
||||
10. [高级主题][10]
|
||||
|
||||
### 远程调试
|
||||
|
||||
远程调试对于嵌入式系统或对不同环境进行调试非常有用。它还在高级调试器操作和与操作系统和硬件的交互之间设置了一个很好的分界线。事实上,像 GDB 和 LLDB 这样的调试器即使在调试本地程序时也可以作为远程调试器运行。一般架构是这样的:
|
||||
|
||||

|
||||
|
||||
调试器是我们通过命令行交互的组件。也许如果你使用的是 IDE,那么在其上有另一个层可以通过_机器接口_与调试器进行通信。在目标机器上(可能与本机一样)有一个<ruby>调试存根<rt>debug stub</rt></ruby> ,理论上它是一个非常小的操作系统调试库的包装程序,它执行所有的低级调试任务,如在地址上设置断点。我说“在理论上”,因为如今调试存根变得越来越大。例如,我机器上的 LLDB 调试存根大小是 7.6MB。调试存根通过使用一些特定于操作系统的功能(在我们的例子中是 `ptrace`)和被调试进程以及通过远程协议的调试器通信。
|
||||
|
||||
最常见的远程调试协议是 GDB 远程协议。这是一种基于文本的数据包格式,用于在调试器和调试存根之间传递命令和信息。我不会详细介绍它,但你可以在[这里][11]进一步阅读。如果你启动 LLDB 并执行命令 `log enable gdb-remote packets`,那么你将获得通过远程协议发送的所有数据包的跟踪信息。在 GDB 上,你可以用 `set remotelogfile <file>` 做同样的事情。
|
||||
|
||||
作为一个简单的例子,这是设置断点的数据包:
|
||||
|
||||
```
|
||||
$Z0,400570,1#43
|
||||
```
|
||||
|
||||
`$` 标记数据包的开始。`Z0` 是插入内存断点的命令。`400570` 和 `1` 是参数,其中前者是设置断点的地址,后者是特定目标的断点类型说明符。最后,`#43` 是校验值,以确保数据没有损坏。
|
||||
|
||||
GDB 远程协议非常易于扩展自定义数据包,这对于实现平台或语言特定的功能非常有用。
|
||||
|
||||
### 共享库和动态加载支持
|
||||
|
||||
调试器需要知道被调试程序加载了哪些共享库,以便它可以设置断点、获取源代码级别的信息和符号等。除查找被动态链接的库之外,调试器还必须跟踪在运行时通过 `dlopen` 加载的库。为了达到这个目的,动态链接器维护一个 _交汇结构体_。该结构体维护共享库描述符的链表,以及一个指向每当更新链表时调用的函数的指针。这个结构存储在 ELF 文件的 `.dynamic` 段中,在程序执行之前被初始化。
|
||||
|
||||
一个简单的跟踪算法:
|
||||
|
||||
* 追踪程序在 ELF 头中查找程序的入口(或者可以使用存储在 `/proc/<pid>/aux` 中的辅助向量)。
|
||||
* 追踪程序在程序的入口处设置一个断点,并开始执行。
|
||||
* 当到达断点时,通过在 ELF 文件中查找 `.dynamic` 的加载地址找到交汇结构体的地址。
|
||||
* 检查交汇结构体以获取当前加载的库的列表。
|
||||
* 链接器更新函数上设置断点。
|
||||
* 每当到达断点时,列表都会更新。
|
||||
* 追踪程序无限循环,继续执行程序并等待信号,直到追踪程序信号退出。
|
||||
|
||||
我给这些概念写了一个小例子,你可以在[这里][12]找到。如果有人有兴趣,我可以将来写得更详细一点。
|
||||
|
||||
### 表达式计算
|
||||
|
||||
表达式计算是程序的一项功能,允许用户在调试程序时对原始源语言中的表达式进行计算。例如,在 LLDB 或 GDB 中,可以执行 `print foo()` 来调用 `foo` 函数并打印结果。
|
||||
|
||||
根据表达式的复杂程度,有几种不同的计算方法。如果表达式只是一个简单的标识符,那么调试器可以查看调试信息,找到该变量并打印出该值,就像我们在本系列最后一部分中所做的那样。如果表达式有点复杂,则可能将代码编译成中间表达式 (IR) 并解释来获得结果。例如,对于某些表达式,LLDB 将使用 Clang 将表达式编译为 LLVM IR 并将其解释。如果表达式更复杂,或者需要调用某些函数,那么代码可能需要 JIT 到目标并在被调试者的地址空间中执行。这涉及到调用 `mmap` 来分配一些可执行内存,然后将编译的代码复制到该块并执行。LLDB 通过使用 LLVM 的 JIT 功能来实现。
|
||||
|
||||
如果你想更多地了解 JIT 编译,我强烈推荐 [Eli Bendersky 关于这个主题的文章][13]。
|
||||
|
||||
### 多线程调试支持
|
||||
|
||||
本系列展示的调试器仅支持单线程应用程序,但是为了调试大多数真实程序,多线程支持是非常需要的。支持这一点的最简单的方法是跟踪线程的创建,并解析 procfs 以获取所需的信息。
|
||||
|
||||
Linux 线程库称为 `pthreads`。当调用 `pthread_create` 时,库会使用 `clone` 系统调用来创建一个新的线程,我们可以用 `ptrace` 跟踪这个系统调用(假设你的内核早于 2.5.46)。为此,你需要在连接到调试器之后设置一些 `ptrace` 选项:
|
||||
|
||||
```
|
||||
ptrace(PTRACE_SETOPTIONS, m_pid, nullptr, PTRACE_O_TRACECLONE);
|
||||
```
|
||||
|
||||
现在当 `clone` 被调用时,该进程将收到我们的老朋友 `SIGTRAP` 信号。对于本系列中的调试器,你可以将一个例子添加到 `handle_sigtrap` 来处理新线程的创建:
|
||||
|
||||
```
|
||||
case (SIGTRAP | (PTRACE_EVENT_CLONE << 8)):
|
||||
//get the new thread ID
|
||||
unsigned long event_message = 0;
|
||||
ptrace(PTRACE_GETEVENTMSG, pid, nullptr, message);
|
||||
|
||||
//handle creation
|
||||
//...
|
||||
```
|
||||
|
||||
一旦收到了,你可以看看 `/proc/<pid>/task/` 并查看内存映射之类来获得所需的所有信息。
|
||||
|
||||
GDB 使用 `libthread_db`,它提供了一堆帮助函数,这样你就不需要自己解析和处理。设置这个库很奇怪,我不会在这展示它如何工作,但如果你想使用它,你可以去阅读[这个教程][14]。
|
||||
|
||||
多线程支持中最复杂的部分是调试器中线程状态的建模,特别是如果你希望支持[不间断模式][15]或当你计算中涉及不止一个 CPU 的某种异构调试。
|
||||
|
||||
### 最后!
|
||||
|
||||
呼!这个系列花了很长时间才写完,但是我在这个过程中学到了很多东西,我希望它是有帮助的。如果你有关于调试或本系列中的任何问题,请在 Twitter [@TartanLlama][16]或评论区联系我。如果你有想看到的其他任何调试主题,让我知道我或许会再发其他的文章。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://blog.tartanllama.xyz/writing-a-linux-debugger-advanced-topics/
|
||||
|
||||
作者:[Simon Brand][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.twitter.com/TartanLlama
|
||||
[1]:https://linux.cn/article-8626-1.html
|
||||
[2]:https://linux.cn/article-8645-1.html
|
||||
[3]:https://linux.cn/article-8663-1.html
|
||||
[4]:https://linux.cn/article-8719-1.html
|
||||
[5]:https://linux.cn/article-8812-1.html
|
||||
[6]:https://linux.cn/article-8813-1.html
|
||||
[7]:https://linux.cn/article-8890-1.html
|
||||
[8]:https://linux.cn/article-8930-1.html
|
||||
[9]:https://linux.cn/article-8936-1.html
|
||||
[10]:https://blog.tartanllama.xyz/writing-a-linux-debugger-advanced-topics/
|
||||
[11]:https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html
|
||||
[12]:https://github.com/TartanLlama/dltrace
|
||||
[13]:http://eli.thegreenplace.net/tag/code-generation
|
||||
[14]:http://timetobleed.com/notes-about-an-odd-esoteric-yet-incredibly-useful-library-libthread_db/
|
||||
[15]:https://sourceware.org/gdb/onlinedocs/gdb/Non_002dStop-Mode.html
|
||||
[16]:https://twitter.com/TartanLlama
|
||||
@@ -0,0 +1,497 @@
|
||||
Up:在几秒钟内部署无服务器应用程序
|
||||
============================================================
|
||||
|
||||

|
||||
|
||||
去年,我[为 Up 规划了一份蓝图][1],其中描述了如何以最小的成本在 AWS 上为大多数构建块创建一个很棒的无服务器环境。这篇文章则是讨论了 [Up][2] 的初始 alpha 版本。
|
||||
|
||||
为什么关注<ruby>无服务器<rt>serverless</rt></ruby>?对于初学者来说,它可以节省成本,因为你可以按需付费,且只为你使用的付费。无服务器方式是自愈的,因为每个请求被隔离并被视作“无状态的”。最后,它可以无限轻松地扩展 —— 没有机器或集群要管理。部署你的代码就行了。
|
||||
|
||||
大约一个月前,我决定开始在 [apex/up][3] 上开发它,并为动态 SVG 版本的 GitHub 用户投票功能写了第一个小型无服务器示例程序 [tj/gh-polls][4]。它运行良好,成本低于每月 1 美元即可为数百万次投票服务,因此我会继续这个项目,看看我是否可以提供开源版本及商业的变体版本。
|
||||
|
||||
其长期的目标是提供“你自己的 Heroku” 的版本,支持许多平台。虽然平台即服务(PaaS)并不新鲜,但无服务器生态系统正在使这种方案日益萎缩。据说,AWS 和其他的供应商经常由于他们提供的灵活性而被人诟病用户体验。Up 将复杂性抽象出来,同时为你提供一个几乎无需运维的解决方案。
|
||||
|
||||
### 安装
|
||||
|
||||
你可以使用以下命令安装 Up,查看这篇[临时文档][5]开始使用。或者如果你使用安装脚本,请下载[二进制版本][6]。(请记住,这个项目还在早期。)
|
||||
|
||||
```
|
||||
curl -sfL https://raw.githubusercontent.com/apex/up/master/install.sh | sh
|
||||
```
|
||||
|
||||
只需运行以下命令随时升级到最新版本:
|
||||
|
||||
```
|
||||
up upgrade
|
||||
```
|
||||
|
||||
你也可以通过 NPM 进行安装:
|
||||
|
||||
```
|
||||
npm install -g up
|
||||
```
|
||||
|
||||
### 功能
|
||||
|
||||
这个早期 alpha 版本提供什么功能?让我们来看看!请记住,Up 不是托管服务,因此你需要一个 AWS 帐户和 [AWS 凭证][8]。如果你对 AWS 不熟悉,你可能需要先停下来,直到熟悉流程。
|
||||
|
||||
我遇到的第一个问题是:up(1) 与 [apex(1)][9] 有何不同?Apex 专注于部署功能,用于管道和事件处理,而 Up 则侧重于应用程序、API 和静态站点,也就是单个可部署单元。Apex 不为你提供 API 网关、SSL 证书或 DNS,也不提供 URL 重写,脚本注入等。
|
||||
|
||||
#### 单命令无服务器应用程序
|
||||
|
||||
Up 可以让你使用单条命令部署应用程序、API 和静态站点。要创建一个应用程序,你只需要一个文件,在 Node.js 的情况下,`./app.js` 监听由 Up 提供的 `PORT'。请注意,如果你使用的是 `package.json` ,则会检测并使用 `start`和 `build` 脚本。
|
||||
|
||||
```
|
||||
const http = require('http')
|
||||
const { PORT = 3000 } = process.env
|
||||
```
|
||||
|
||||
```
|
||||
http.createServer((req, res) => {
|
||||
res.end('Hello World\n')
|
||||
}).listen(PORT)
|
||||
```
|
||||
|
||||
额外的[运行时环境][10]也支持开箱可用,例如用于 Golang 的 “main.go”,所以你可以在几秒钟内部署 Golang、Python、Crystal 或 Node.js 应用程序。
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
addr := ":" + os.Getenv("PORT")
|
||||
http.HandleFunc("/", hello)
|
||||
log.Fatal(http.ListenAndServe(addr, nil))
|
||||
}
|
||||
|
||||
func hello(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "Hello World from Go")
|
||||
}
|
||||
```
|
||||
|
||||
要部署应用程序输入 `up` 来创建所需的资源并部署应用程序本身。这里没有模糊不清的地方,一旦它说“完成”了那就完成了,该应用程序立即可用 —— 没有远程构建过程。
|
||||
|
||||

|
||||
|
||||
后续的部署将会更快,因为栈已被配置:
|
||||
|
||||

|
||||
|
||||
使用 `up url --open` 测试你的程序,以在浏览器中浏览它,`up url --copy` 可以将 URL 保存到剪贴板,或者可以尝试使用 curl:
|
||||
|
||||
```
|
||||
curl `up url`
|
||||
Hello World
|
||||
```
|
||||
|
||||
要删除应用程序及其资源,只需输入 `up stack delete`:
|
||||
|
||||

|
||||
|
||||
例如,使用 `up staging` 或 `up production` 和 `up url --open production` 部署到预发布或生产环境。请注意,自定义域名尚不可用,[它们将很快可用][11]。之后,你还可以将版本“推广”到其他环境。
|
||||
|
||||
#### 反向代理
|
||||
|
||||
Up 的一个独特的功能是,它不仅仅是简单地部署代码,它将一个 Golang 反向代理放在应用程序的前面。这提供了许多功能,如 URL 重写、重定向、脚本注入等等,我们将在后面进一步介绍。
|
||||
|
||||
#### 基础设施即代码
|
||||
|
||||
在配置方面,Up 遵循现代最佳实践,因此对基础设施的更改都可以在部署之前预览,并且 IAM 策略的使用还可以限制开发人员访问以防止事故发生。一个附带的好处是它有助于自动记录你的基础设施。
|
||||
|
||||
以下是使用 Let's Encrypt 通过 AWS ACM 配置一些(虚拟)DNS 记录和免费 SSL 证书的示例。
|
||||
|
||||
```
|
||||
{
|
||||
"name": "app",
|
||||
"dns": {
|
||||
"myapp.com": [
|
||||
{
|
||||
"name": "myapp.com",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"value": ["35.161.83.243"]
|
||||
},
|
||||
{
|
||||
"name": "blog.myapp.com",
|
||||
"type": "CNAME",
|
||||
"ttl": 300,
|
||||
"value": ["34.209.172.67"]
|
||||
},
|
||||
{
|
||||
"name": "api.myapp.com",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"value": ["54.187.185.18"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"certs": [
|
||||
{
|
||||
"domains": ["myapp.com", "*.myapp.com"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
当你首次通过 `up` 部署应用程序时,需要所有的权限,它为你创建 API 网关、Lambda 函数、ACM 证书、Route53 DNS 记录等。
|
||||
|
||||
[ChangeSets][12] 尚未实现,但你能使用 `up stack plan` 预览进一步的更改,并使用 `up stack apply` 提交,这与 Terraform 非常相似。
|
||||
|
||||
详细信息请参阅[配置文档][13]。
|
||||
|
||||
#### 全球部署
|
||||
|
||||
`regions` 数组可以指定应用程序的目标区域。例如,如果你只对单个地区感兴趣,请使用:
|
||||
|
||||
```
|
||||
{
|
||||
"regions": ["us-west-2"]
|
||||
}
|
||||
```
|
||||
|
||||
如果你的客户集中在北美,你可能需要使用美国和加拿大所有地区:
|
||||
|
||||
```
|
||||
{
|
||||
"regions": ["us-*", "ca-*"]
|
||||
}
|
||||
```
|
||||
|
||||
最后,你可以使用 AWS 目前支持的所有 14 个地区:
|
||||
|
||||
```
|
||||
{
|
||||
"regions": ["*"]
|
||||
}
|
||||
```
|
||||
|
||||
多区域支持仍然是一个正在进行的工作,因为需要一些新的 AWS 功能来将它们结合在一起。
|
||||
|
||||
#### 静态文件服务
|
||||
|
||||
Up 默认支持静态文件服务,并带有 HTTP 缓存支持,因此你可以在应用程序前使用 CloudFront 或任何其他 CDN 来大大减少延迟。
|
||||
|
||||
当 `type` 为 `static` 时,默认情况下的工作目录是 `.`,但是你也可以提供一个 `static.dir`:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "app",
|
||||
"type": "static",
|
||||
"static": {
|
||||
"dir": "public"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 构建钩子
|
||||
|
||||
构建钩子允许你在部署或执行其他操作时定义自定义操作。一个常见的例子是使用 Webpack 或 Browserify 捆绑 Node.js 应用程序,这大大减少了文件大小,因为 node 模块是_很大_的。
|
||||
|
||||
```
|
||||
{
|
||||
"name": "app",
|
||||
"hooks": {
|
||||
"build": "browserify --node server.js > app.js",
|
||||
"clean": "rm app.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 脚本和样式表插入
|
||||
|
||||
Up 允许你插入脚本和样式,无论是内联方式或声明路径。它甚至支持一些“罐头”脚本,用于 Google Analytics(分析)和 [Segment][14],只需复制并粘贴你的写入密钥即可。
|
||||
|
||||
```
|
||||
{
|
||||
"name": "site",
|
||||
"type": "static",
|
||||
"inject": {
|
||||
"head": [
|
||||
{
|
||||
"type": "segment",
|
||||
"value": "API_KEY"
|
||||
},
|
||||
{
|
||||
"type": "inline style",
|
||||
"file": "/css/primer.css"
|
||||
}
|
||||
],
|
||||
"body": [
|
||||
{
|
||||
"type": "script",
|
||||
"value": "/app.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 重写和重定向
|
||||
|
||||
Up 通过 `redirects` 对象支持重定向和 URL 重写,该对象将路径模式映射到新位置。如果省略 `status` 参数(或值为 200),那么它是重写,否则是重定向。
|
||||
|
||||
```
|
||||
{
|
||||
"name": "app",
|
||||
"type": "static",
|
||||
"redirects": {
|
||||
"/blog": {
|
||||
"location": "https://blog.apex.sh/",
|
||||
"status": 301
|
||||
},
|
||||
"/docs/:section/guides/:guide": {
|
||||
"location": "/help/:section/:guide",
|
||||
"status": 302
|
||||
},
|
||||
"/store/*": {
|
||||
"location": "/shop/:splat"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
用于重写的常见情况是 SPA(单页面应用程序),你希望不管路径如何都提供 `index.html`,当然除非文件存在。
|
||||
|
||||
```
|
||||
{
|
||||
"name": "app",
|
||||
"type": "static",
|
||||
"redirects": {
|
||||
"/*": {
|
||||
"location": "/",
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果要强制实施该规则,无论文件是否存在,只需添加 `"force": true` 。
|
||||
|
||||
#### 环境变量
|
||||
|
||||
密码将在下一个版本中有,但是现在支持纯文本环境变量:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "api",
|
||||
"environment": {
|
||||
"API_FEATURE_FOO": "1",
|
||||
"API_FEATURE_BAR": "0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### CORS 支持
|
||||
|
||||
[CORS][16] 支持允许你指定哪些(如果有的话)域可以从浏览器访问你的 API。如果你希望允许任何网站访问你的 API,只需启用它:
|
||||
|
||||
```
|
||||
{
|
||||
"cors": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你还可以自定义访问,例如仅将 API 访问限制为你的前端或 SPA。
|
||||
|
||||
```
|
||||
{
|
||||
"cors": {
|
||||
"allowed_origins": ["https://myapp.com"],
|
||||
"allowed_methods": ["HEAD", "GET", "POST", "PUT", "DELETE"],
|
||||
"allowed_headers": ["Content-Type", "Authorization"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 日志
|
||||
|
||||
对于 $0.5/GB 的低价格,你可以使用 CloudWatch 日志进行结构化日志查询和跟踪。Up 实现了一种用于改进 CloudWatch 提供的自定义[查询语言][18],专门用于查询结构化 JSON 日志。
|
||||
|
||||

|
||||
|
||||
你可以查询现有日志:
|
||||
|
||||
```
|
||||
up logs
|
||||
```
|
||||
|
||||
跟踪在线日志:
|
||||
|
||||
```
|
||||
up logs -f
|
||||
```
|
||||
|
||||
或者对其中任一个进行过滤,例如只显示耗时超过 5 毫秒的 200 个 GET/HEAD 请求:
|
||||
|
||||
```
|
||||
up logs 'method in ("GET", "HEAD") status = 200 duration >= 5'
|
||||
```
|
||||
|
||||

|
||||
|
||||
查询语言是非常灵活的,这里有更多来自于 `up help logs` 的例子
|
||||
|
||||
```
|
||||
### 显示过去 5 分钟的日志
|
||||
$ up logs
|
||||
|
||||
### 显示过去 30 分钟的日志
|
||||
$ up logs -s 30m
|
||||
|
||||
### 显示过去 5 小时的日志
|
||||
$ up logs -s 5h
|
||||
|
||||
### 实时显示日志
|
||||
$ up logs -f
|
||||
|
||||
### 显示错误日志
|
||||
$ up logs error
|
||||
|
||||
### 显示错误和致命错误日志
|
||||
$ up logs 'error or fatal'
|
||||
|
||||
### 显示非 info 日志
|
||||
$ up logs 'not info'
|
||||
|
||||
### 显示特定消息的日志
|
||||
$ up logs 'message = "user login"'
|
||||
|
||||
### 显示超时 150ms 的 200 响应
|
||||
$ up logs 'status = 200 duration > 150'
|
||||
|
||||
### 显示 4xx 和 5xx 响应
|
||||
$ up logs 'status >= 400'
|
||||
|
||||
### 显示用户邮件包含 @apex.sh 的日志
|
||||
$ up logs 'user.email contains "@apex.sh"'
|
||||
|
||||
### 显示用户邮件以 @apex.sh 结尾的日志
|
||||
$ up logs 'user.email = "*@apex.sh"'
|
||||
|
||||
### 显示用户邮件以 tj@ 开始的日志
|
||||
$ up logs 'user.email = "tj@*"'
|
||||
|
||||
### 显示路径 /tobi 和 /loki 下的错误日志
|
||||
$ up logs 'error and (path = "/tobi" or path = "/loki")'
|
||||
|
||||
### 和上面一样,用 in 显示
|
||||
$ up logs 'error and path in ("/tobi", "/loki")'
|
||||
|
||||
### 更复杂的查询方式
|
||||
$ up logs 'method in ("POST", "PUT") ip = "207.*" status = 200 duration >= 50'
|
||||
|
||||
### 将 JSON 格式的错误日志发送给 jq 工具
|
||||
$ up logs error | jq
|
||||
```
|
||||
|
||||
请注意,`and` 关键字是暗含的,虽然你也可以使用它。
|
||||
|
||||
#### 冷启动时间
|
||||
|
||||
这是 AWS Lambda 平台的特性,但冷启动时间通常远远低于 1 秒,在未来,我计划提供一个选项来保持它们在线。
|
||||
|
||||
#### 配置验证
|
||||
|
||||
`up config` 命令输出解析后的配置,有默认值和推断的运行时设置 - 它也起到验证配置的双重目的,因为任何错误都会导致退出状态 > 0。
|
||||
|
||||
#### 崩溃恢复
|
||||
|
||||
使用 Up 作为反向代理的另一个好处是执行崩溃恢复 —— 在崩溃后重新启动服务器,并在将错误的响应发送给客户端之前重新尝试该请求。
|
||||
|
||||
例如,假设你的 Node.js 程序由于间歇性数据库问题而导致未捕获的异常崩溃,Up 可以在响应客户端之前重试该请求。之后这个行为会更加可定制。
|
||||
|
||||
#### 适合持续集成
|
||||
|
||||
很难说这是一个功能,但是感谢 Golang 相对较小和独立的二进制文件,你可以在一两秒中在 CI 中安装 Up。
|
||||
|
||||
#### HTTP/2
|
||||
|
||||
Up 通过 API 网关支持 HTTP/2,对于服务很多资源的应用和站点可以减少延迟。我将来会对许多平台进行更全面的测试,但是 Up 的延迟已经很好了:
|
||||
|
||||

|
||||
|
||||
#### 错误页面
|
||||
|
||||
Up 提供了一个默认错误页面,如果你要提供支持电子邮件或调整颜色,你可以使用 `error_pages` 自定义。
|
||||
|
||||
```
|
||||
{
|
||||
"name": "site",
|
||||
"type": "static",
|
||||
"error_pages": {
|
||||
"variables": {
|
||||
"support_email": "support@apex.sh",
|
||||
"color": "#228ae6"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
默认情况下,它看上去像这样:
|
||||
|
||||

|
||||
|
||||
如果你想提供自定义模板,你可以创建以下一个或多个文件。特定文件优先。
|
||||
|
||||
* `error.html` – 匹配任何 4xx 或 5xx
|
||||
* `5xx.html` – 匹配任何 5xx 错误
|
||||
* `4xx.html` – 匹配任何 4xx 错误
|
||||
* `CODE.html` – 匹配一个特定的代码,如 404.html
|
||||
|
||||
查看[文档][22]阅读更多有关模板的信息。
|
||||
|
||||
### 伸缩和成本
|
||||
|
||||
你已经做了这么多,但是 Up 怎么伸缩?目前,API 网关和 AWS 是目标平台,因此你无需进行任何更改即可扩展,只需部署代码即可完成。你只需按需支付实际使用的数量并且无需人工干预。
|
||||
|
||||
AWS 每月免费提供 1,000,000 个请求,但你可以使用 [http://serverlesscalc.com][23] 来插入预期流量。在未来 Up 将提供更多的平台,所以如果一个成本过高,你可以迁移到另一个!
|
||||
|
||||
### 未来
|
||||
|
||||
目前为止就这样了!它可能看起来不是很多,但它已经超过 10,000 行代码,并且我刚刚开始开发。看看这个问题队列,假设项目可持续发展,看看未来会有什么期待。
|
||||
|
||||
如果你发现这个免费版本有用,请考虑在 [OpenCollective][24] 上捐赠我,因为我没有任何工作。我将在短期内开发早期专业版,对早期用户有年费优惠。专业或企业版也将提供源码,因此可以进行内部修复和自定义。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://medium.freecodecamp.org/up-b3db1ca930ee
|
||||
|
||||
作者:[TJ Holowaychuk][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://medium.freecodecamp.org/@tjholowaychuk?source=post_header_lockup
|
||||
[1]:https://medium.com/@tjholowaychuk/blueprints-for-up-1-5f8197179275
|
||||
[2]:https://github.com/apex/up
|
||||
[3]:https://github.com/apex/up
|
||||
[4]:https://github.com/tj/gh-polls
|
||||
[5]:https://github.com/apex/up/tree/master/docs
|
||||
[6]:https://github.com/apex/up/releases
|
||||
[7]:https://raw.githubusercontent.com/apex/up/master/install.sh
|
||||
[8]:https://github.com/apex/up/blob/master/docs/aws-credentials.md
|
||||
[9]:https://github.com/apex/apex
|
||||
[10]:https://github.com/apex/up/blob/master/docs/runtimes.md
|
||||
[11]:https://github.com/apex/up/issues/166
|
||||
[12]:https://github.com/apex/up/issues/115
|
||||
[13]:https://github.com/apex/up/blob/master/docs/configuration.md
|
||||
[14]:https://segment.com/
|
||||
[15]:https://blog.apex.sh/
|
||||
[16]:https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
|
||||
[17]:https://myapp.com/
|
||||
[18]:https://github.com/apex/up/blob/master/internal/logs/parser/grammar.peg
|
||||
[19]:http://twitter.com/apex
|
||||
[20]:http://twitter.com/apex
|
||||
[21]:http://twitter.com/apex
|
||||
[22]:https://github.com/apex/up/blob/master/docs/configuration.md#error-pages
|
||||
[23]:http://serverlesscalc.com/
|
||||
[24]:https://opencollective.com/apex-up
|
||||
@@ -0,0 +1,703 @@
|
||||
论 HTTP 性能,Go 与 .NET Core 一争雌雄
|
||||
============================================================
|
||||
|
||||

|
||||
|
||||
朋友们,你们好!
|
||||
|
||||
近来,我听到了大量的关于新出的 .NET Core 和其性能的讨论,尤其在 Web 服务方面的讨论更甚。
|
||||
|
||||
因为是新出的,我不想立马就比较两个不同的东西,所以我耐心等待,想等发布更稳定的版本后再进行。
|
||||
|
||||
本周一(8 月 14 日),微软[发布 .NET Core 2.0 版本][7],因此,我准备开始。您们认为呢?
|
||||
|
||||
如前面所提的,我们会比较它们相同的东西,比如应用程序、预期响应及运行时的稳定性,所以我们不会把像对 JSON 或者 XML 的编码、解码这些烦多的事情加入比较游戏中来,仅仅只会使用简单的文本消息。为了公平起见,我们会分别使用 Go 和 .NET Core 的 [MVC 架构模式][8]。
|
||||
|
||||
### 参赛选手
|
||||
|
||||
[Go][9] (或称 Golang): 是一种[快速增长][10]的开源编程语言,旨在构建出简单、快捷和稳定可靠的应用软件。
|
||||
|
||||
用于支持 Go 语言的 MVC web 框架并不多,还好我们找到了 Iris ,可胜任此工作。
|
||||
|
||||
[Iris][11]: 支持 Go 语言的快速、简单和高效的微型 Web 框架。它为您的下一代网站、API 或分布式应用程序奠定了精美的表现方式和易于使用的基础。
|
||||
|
||||
[C#][12]: 是一种通用的、面向对象的编程语言。其开发团队由 [Anders Hejlsberg][13] 领导。
|
||||
|
||||
[.NET Core][14]: 跨平台,可以在极少时间内开发出高性能的应用程序。
|
||||
|
||||
可从 [https://golang.org/dl][15] 下载 Go ,从 [https://www.microsoft.com/net/core][16] 下载 .NET Core。
|
||||
|
||||
在下载和安装好这些软件后,还需要为 Go 安装 Iris。安装很简单,仅仅只需要打开终端,然后执行如下语句:
|
||||
|
||||
```
|
||||
go get -u github.com/kataras/iris
|
||||
```
|
||||
|
||||
### 基准
|
||||
|
||||
#### 硬件
|
||||
|
||||
* 处理器: Intel(R) Core(TM) i7–4710HQ CPU @ 2.50GHz 2.50GHz
|
||||
* 内存: 8.00 GB
|
||||
|
||||
#### 软件
|
||||
|
||||
* 操作系统: 微软 Windows [10.0.15063 版本], 电源计划设置为“高性能”
|
||||
* HTTP 基准工具: [https://github.com/codesenberg/bombardier][1], 使用最新的 1.1 版本。
|
||||
* .NET Core: [https://www.microsoft.com/net/core][2], 使用最新的 2.0 版本。
|
||||
* Iris: [https://github.com/kataras/iris][3], 使用基于 [Go 1.8.3][4] 构建的最新 8.3 版本。
|
||||
|
||||
两个应用程序都通过请求路径 “api/values/{id}” 返回文本“值”。
|
||||
|
||||
##### .NET Core MVC
|
||||
|
||||

|
||||
|
||||
Logo 由 [Pablo Iglesias][5] 设计。
|
||||
|
||||
可以使用 `dotnet new webapi` 命令创建项目,其 `webapi` 模板会为您生成代码,代码包含 `GET` 请求方法的 `返回“值”`。
|
||||
|
||||
源代码:
|
||||
|
||||
```
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace netcore_mvc
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BuildWebHost(args).Run();
|
||||
}
|
||||
|
||||
public static IWebHost BuildWebHost(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace netcore_mvc
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMvcCore();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace netcore_mvc.Controllers
|
||||
{
|
||||
// ValuesController is the equivalent
|
||||
// `ValuesController` of the Iris 8.3 mvc application.
|
||||
[Route("api/[controller]")]
|
||||
public class ValuesController : Controller
|
||||
{
|
||||
// Get handles "GET" requests to "api/values/{id}".
|
||||
[HttpGet("{id}")]
|
||||
public string Get(int id)
|
||||
{
|
||||
return "value";
|
||||
}
|
||||
|
||||
// Put handles "PUT" requests to "api/values/{id}".
|
||||
[HttpPut("{id}")]
|
||||
public void Put(int id, [FromBody]string value)
|
||||
{
|
||||
}
|
||||
|
||||
// Delete handles "DELETE" requests to "api/values/{id}".
|
||||
[HttpDelete("{id}")]
|
||||
public void Delete(int id)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
运行 .NET Core web 服务项目:
|
||||
|
||||
```
|
||||
$ cd netcore-mvc
|
||||
$ dotnet run -c Release
|
||||
Hosting environment: Production
|
||||
Content root path: C:\mygopath\src\github.com\kataras\iris\_benchmarks\netcore-mvc
|
||||
Now listening on: http://localhost:5000
|
||||
Application started. Press Ctrl+C to shut down.
|
||||
```
|
||||
|
||||
运行和定位 HTTP 基准工具:
|
||||
|
||||
```
|
||||
$ bombardier -c 125 -n 5000000 http://localhost:5000/api/values/5
|
||||
Bombarding http://localhost:5000/api/values/5 with 5000000 requests using 125 connections
|
||||
5000000 / 5000000 [=====================================================] 100.00% 2m3s
|
||||
Done!
|
||||
Statistics Avg Stdev Max
|
||||
Reqs/sec 40226.03 8724.30 161919
|
||||
Latency 3.09ms 1.40ms 169.12ms
|
||||
HTTP codes:
|
||||
1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0
|
||||
others - 0
|
||||
Throughput: 8.91MB/s
|
||||
```
|
||||
|
||||
##### Iris MVC
|
||||
|
||||

|
||||
|
||||
Logo 由 [Santosh Anand][6] 设计。
|
||||
|
||||
源代码:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/_benchmarks/iris-mvc/controllers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Controller("/api/values/{id}", new(controllers.ValuesController))
|
||||
app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker)
|
||||
}
|
||||
view raw
|
||||
```
|
||||
|
||||
```
|
||||
package controllers
|
||||
|
||||
import "github.com/kataras/iris/mvc"
|
||||
|
||||
// ValuesController is the equivalent
|
||||
// `ValuesController` of the .net core 2.0 mvc application.
|
||||
type ValuesController struct {
|
||||
mvc.Controller
|
||||
}
|
||||
|
||||
// Get handles "GET" requests to "api/values/{id}".
|
||||
func (vc *ValuesController) Get() {
|
||||
// id,_ := vc.Params.GetInt("id")
|
||||
vc.Ctx.WriteString("value")
|
||||
}
|
||||
|
||||
// Put handles "PUT" requests to "api/values/{id}".
|
||||
func (vc *ValuesController) Put() {}
|
||||
|
||||
// Delete handles "DELETE" requests to "api/values/{id}".
|
||||
func (vc *ValuesController) Delete() {}
|
||||
```
|
||||
|
||||
运行 Go web 服务项目:
|
||||
|
||||
```
|
||||
$ cd iris-mvc
|
||||
$ go run main.go
|
||||
Now listening on: http://localhost:5000
|
||||
Application started. Press CTRL+C to shut down.
|
||||
```
|
||||
|
||||
运行和定位 HTTP 基准工具:
|
||||
|
||||
```
|
||||
$ bombardier -c 125 -n 5000000 http://localhost:5000/api/values/5
|
||||
Bombarding http://localhost:5000/api/values/5 with 5000000 requests using 125 connections
|
||||
5000000 / 5000000 [======================================================] 100.00% 47s
|
||||
Done!
|
||||
Statistics Avg Stdev Max
|
||||
Reqs/sec 105643.81 7687.79 122564
|
||||
Latency 1.18ms 366.55us 22.01ms
|
||||
HTTP codes:
|
||||
1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0
|
||||
others - 0
|
||||
Throughput: 19.65MB/s
|
||||
```
|
||||
|
||||
想通过图片来理解的人,我也把我的屏幕截屏出来了!
|
||||
|
||||
请点击[这儿][23]可以看到这些屏幕快照。
|
||||
|
||||
#### 总结
|
||||
|
||||
* 完成 `5000000 个请求`的时间 - 越短越好。
|
||||
* 请求次数/每秒 - 越大越好。
|
||||
* 等待时间 — 越短越好。
|
||||
* 吞吐量 — 越大越好。
|
||||
* 内存使用 — 越小越好。
|
||||
* LOC (代码行数) — 越少越好。
|
||||
|
||||
.NET Core MVC 应用程序,使用 86 行代码,运行 2 分钟 8 秒,每秒接纳 39311.56 个请求,平均 3.19ms 等待,最大时到 229.73ms,内存使用大约为 126MB(不包括 dotnet 框架)。
|
||||
|
||||
Iris MVC 应用程序,使用 27 行代码,运行 47 秒,每秒接纳 105643.71 个请求,平均 1.18ms 等待,最大时到 22.01ms,内存使用大约为 12MB。
|
||||
|
||||
> 还有另外一个模板的基准,滚动到底部。
|
||||
|
||||
**2017 年 8 月 20 号更新**
|
||||
|
||||
[Josh Clark] [24] 和 [Scott Hanselman] [25]在此 [tweet 评论] [26]上指出,.NET Core `Startup.cs` 文件中 `services.AddMvc();` 这行可以替换为 `services.AddMvcCore();`。我听从他们的意见,修改代码,重新运行基准,该文章的 .NET Core 应用程序的基准输出已经修改。
|
||||
|
||||
@topdawgevh @shanselman 他们也在使用 `AddMvc()` 而不是 `AddMvcCore()` ...,难道都不包含中间件?
|
||||
|
||||
— @clarkis117
|
||||
|
||||
@clarkis117 @topdawgevh Cool @MakisMaropoulos @ben_a_adams @davidfowl 我们来看看。认真学习下怎么使用更简单的性能默认值。
|
||||
|
||||
— @shanselman
|
||||
|
||||
@shanselman @clarkis117 @topdawgevh @ben_a_adams @davidfowl @shanselman @ben_a_adams @davidfowl 谢谢您们的反馈意见。我已经修改,更新了结果,没什么不同。对其它的建议,我非常欢迎。
|
||||
|
||||
— @MakisMaropoulos
|
||||
|
||||
> 它有点稍微的不同但相差不大(从 8.61MB/s 到 8.91MB/s)
|
||||
|
||||
想要了解跟 `services.AddMvc()` 标准比较结果的,可以点击[这儿][27]。
|
||||
|
||||
### 想再多了解点儿吗?
|
||||
|
||||
我们再制定一个基准,产生 `1000000 次请求`,这次会通过视图引擎由模板生成 `HTML` 页面。
|
||||
|
||||
#### .NET Core MVC 使用的模板
|
||||
|
||||
```
|
||||
using System;
|
||||
|
||||
namespace netcore_mvc_templates.Models
|
||||
{
|
||||
public class ErrorViewModel
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public int Code { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using netcore_mvc_templates.Models;
|
||||
|
||||
namespace netcore_mvc_templates.Controllers
|
||||
{
|
||||
public class HomeController : Controller
|
||||
{
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult About()
|
||||
{
|
||||
ViewData["Message"] = "Your application description page.";
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Contact()
|
||||
{
|
||||
ViewData["Message"] = "Your contact page.";
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel { Title = "Error", Code = 500});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace netcore_mvc_templates
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BuildWebHost(args).Run();
|
||||
}
|
||||
|
||||
public static IWebHost BuildWebHost(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace netcore_mvc_templates
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
/* An unhandled exception was thrown by the application.
|
||||
System.InvalidOperationException: No service for type
|
||||
'Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory' has been registered.
|
||||
Solution: Use AddMvc() instead of AddMvcCore() in Startup.cs and it will work.
|
||||
*/
|
||||
// services.AddMvcCore();
|
||||
services.AddMvc();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
name: "default",
|
||||
template: "{controller=Home}/{action=Index}/{id?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
/*
|
||||
wwwroot/css
|
||||
wwwroot/images
|
||||
wwwroot/js
|
||||
wwwroot/lib
|
||||
wwwroot/favicon.ico
|
||||
|
||||
|
||||
Views/Shared/_Layout.cshtml
|
||||
Views/Shared/Error.cshtml
|
||||
|
||||
Views/Home/About.cshtml
|
||||
Views/Home/Contact.cshtml
|
||||
Views/Home/Index.cshtml
|
||||
|
||||
These files are quite long to be shown in this article but you can view them at:
|
||||
https://github.com/kataras/iris/tree/master/_benchmarks/netcore-mvc-templates
|
||||
|
||||
```
|
||||
|
||||
|
||||
运行 .NET Core 服务项目:
|
||||
|
||||
```
|
||||
$ cd netcore-mvc-templates
|
||||
$ dotnet run -c Release
|
||||
Hosting environment: Production
|
||||
Content root path: C:\mygopath\src\github.com\kataras\iris\_benchmarks\netcore-mvc-templates
|
||||
Now listening on: http://localhost:5000
|
||||
Application started. Press Ctrl+C to shut down.
|
||||
```
|
||||
|
||||
运行 HTTP 基准工具:
|
||||
|
||||
```
|
||||
Bombarding http://localhost:5000 with 1000000 requests using 125 connections
|
||||
1000000 / 1000000 [====================================================] 100.00% 1m20s
|
||||
Done!
|
||||
Statistics Avg Stdev Max
|
||||
Reqs/sec 11738.60 7741.36 125887
|
||||
Latency 10.10ms 22.10ms 1.97s
|
||||
HTTP codes:
|
||||
1xx — 0, 2xx — 1000000, 3xx — 0, 4xx — 0, 5xx — 0
|
||||
others — 0
|
||||
Throughput: 89.03MB/s
|
||||
```
|
||||
|
||||
#### Iris MVC 使用的模板
|
||||
|
||||
```
|
||||
package controllers
|
||||
|
||||
import "github.com/kataras/iris/mvc"
|
||||
|
||||
type AboutController struct{ mvc.Controller }
|
||||
|
||||
func (c *AboutController) Get() {
|
||||
c.Data["Title"] = "About"
|
||||
c.Data["Message"] = "Your application description page."
|
||||
c.Tmpl = "about.html"
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
package controllers
|
||||
|
||||
import "github.com/kataras/iris/mvc"
|
||||
|
||||
type ContactController struct{ mvc.Controller }
|
||||
|
||||
func (c *ContactController) Get() {
|
||||
c.Data["Title"] = "Contact"
|
||||
c.Data["Message"] = "Your contact page."
|
||||
c.Tmpl = "contact.html"
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
package models
|
||||
|
||||
// HTTPError a silly structure to keep our error page data.
|
||||
type HTTPError struct {
|
||||
Title string
|
||||
Code int
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
package controllers
|
||||
|
||||
import "github.com/kataras/iris/mvc"
|
||||
|
||||
type IndexController struct{ mvc.Controller }
|
||||
|
||||
func (c *IndexController) Get() {
|
||||
c.Data["Title"] = "Home Page"
|
||||
c.Tmpl = "index.html"
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_benchmarks/iris-mvc-templates/controllers"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
const (
|
||||
// templatesDir is the exactly the same path that .NET Core is using for its templates,
|
||||
// in order to reduce the size in the repository.
|
||||
// Change the "C\\mygopath" to your own GOPATH.
|
||||
templatesDir = "C:\\mygopath\\src\\github.com\\kataras\\iris\\_benchmarks\\netcore-mvc-templates\\wwwroot"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Configure(configure)
|
||||
|
||||
app.Controller("/", new(controllers.IndexController))
|
||||
app.Controller("/about", new(controllers.AboutController))
|
||||
app.Controller("/contact", new(controllers.ContactController))
|
||||
|
||||
app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker)
|
||||
}
|
||||
|
||||
func configure(app *iris.Application) {
|
||||
app.RegisterView(iris.HTML("./views", ".html").Layout("shared/layout.html"))
|
||||
app.StaticWeb("/public", templatesDir)
|
||||
app.OnAnyErrorCode(onError)
|
||||
}
|
||||
|
||||
type err struct {
|
||||
Title string
|
||||
Code int
|
||||
}
|
||||
|
||||
func onError(ctx context.Context) {
|
||||
ctx.ViewData("", err{"Error", ctx.GetStatusCode()})
|
||||
ctx.View("shared/error.html")
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
/*
|
||||
../netcore-mvc-templates/wwwroot/css
|
||||
../netcore-mvc-templates/wwwroot/images
|
||||
../netcore-mvc-templates/wwwroot/js
|
||||
../netcore-mvc-templates/wwwroot/lib
|
||||
../netcore-mvc-templates/wwwroot/favicon.ico
|
||||
views/shared/layout.html
|
||||
views/shared/error.html
|
||||
views/about.html
|
||||
views/contact.html
|
||||
views/index.html
|
||||
These files are quite long to be shown in this article but you can view them at:
|
||||
https://github.com/kataras/iris/tree/master/_benchmarks/iris-mvc-templates
|
||||
*/
|
||||
|
||||
```
|
||||
|
||||
运行 Go 服务项目:
|
||||
|
||||
```
|
||||
$ cd iris-mvc-templates
|
||||
$ go run main.go
|
||||
Now listening on: http://localhost:5000
|
||||
Application started. Press CTRL+C to shut down.
|
||||
|
||||
```
|
||||
|
||||
运行 HTTP 基准工具:
|
||||
|
||||
```
|
||||
Bombarding http://localhost:5000 with 1000000 requests using 125 connections
|
||||
1000000 / 1000000 [======================================================] 100.00% 37s
|
||||
Done!
|
||||
Statistics Avg Stdev Max
|
||||
Reqs/sec 26656.76 1944.73 31188
|
||||
Latency 4.69ms 1.20ms 22.52ms
|
||||
HTTP codes:
|
||||
1xx — 0, 2xx — 1000000, 3xx — 0, 4xx — 0, 5xx — 0
|
||||
others — 0
|
||||
Throughput: 192.51MB/s
|
||||
```
|
||||
|
||||
#### 总结
|
||||
|
||||
* 完成 `1000000 个请求`的时间 - 越短越好。
|
||||
* 请求次数/每秒 - 越大越好。
|
||||
* 等待时间 — 越短越好。
|
||||
* 内存使用 — 越小越好。
|
||||
* 吞吐量 — 越大越好。
|
||||
|
||||
.NET Core MVC 模板应用程序,运行 1 分钟 20 秒,每秒接纳 11738.60 个请求,同时每秒生成 89.03M 页面,平均 10.10ms 等待,最大时到 1.97s,内存使用大约为 193MB(不包括 dotnet 框架)。
|
||||
|
||||
Iris MVC 模板应用程序,运行 37 秒,每秒接纳 26656.76 个请求,同时每秒生成 192.51M 页面,平均 1.18ms 等待,最大时到 22.52ms,内存使用大约为 17MB。
|
||||
|
||||
### 接下来呢?
|
||||
|
||||
[这里][32]有上面所示的源代码,请下载下来,在您本地以同样的基准运行,然后把运行结果在这儿给大家分享。
|
||||
|
||||
想添加 Go 或 C# .net core WEB 服务框架到列表的朋友请向[这个仓库][33]的 `_benchmarks ` 目录推送 PR。
|
||||
|
||||
我也需要亲自感谢下 [dev.to][34] 团队,感谢把我的这篇文章分享到他们的 Twitter 账户。
|
||||
|
||||
|
||||
感谢大家真心反馈,玩得开心!
|
||||
|
||||
#### 更新 : 2017 年 8 月 21 ,周一
|
||||
|
||||
很多人联系我,希望看到一个基于 .NET Core 的较低级别 Kestrel 的基准测试文章。
|
||||
|
||||
因此我完成了,请点击下面的[链接][35]来了解 Kestrel 和 Iris 之间的性能差异,它还包含一个会话存储管理基准!
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://hackernoon.com/go-vs-net-core-in-terms-of-http-performance-7535a61b67b8
|
||||
|
||||
作者:[Gerasimos Maropoulos][a]
|
||||
译者:[runningwater](https://github.com/runningwater)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://hackernoon.com/@kataras?source=post_header_lockup
|
||||
[1]:https://github.com/codesenberg/bombardier
|
||||
[2]:https://www.microsoft.com/net/core
|
||||
[3]:https://github.com/kataras/iris
|
||||
[4]:https://golang.org/
|
||||
[5]:https://github.com/campusMVP/dotnetCoreLogoPack
|
||||
[6]:https://github.com/santoshanand
|
||||
[7]:https://blogs.msdn.microsoft.com/dotnet/2017/08/14/announcing-net-core-2-0/
|
||||
[8]:https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
|
||||
[9]:https://golang.org/
|
||||
[10]:http://www.tiobe.com/tiobe-index/
|
||||
[11]:http://iris-go.com/
|
||||
[12]:https://en.wikipedia.org/wiki/C_Sharp_%28programming_language%29
|
||||
[13]:https://twitter.com/ahejlsberg
|
||||
[14]:https://www.microsoft.com/net/
|
||||
[15]:https://golang.org/dl
|
||||
[16]:https://www.microsoft.com/net/core
|
||||
[17]:http://localhost:5000/
|
||||
[18]:http://localhost:5000/api/values/5
|
||||
[19]:http://localhost:5000/api/values/5
|
||||
[20]:http://localhost:5000/
|
||||
[21]:http://localhost:5000/api/values/5
|
||||
[22]:http://localhost:5000/api/values/5
|
||||
[23]:https://github.com/kataras/iris/tree/master/_benchmarks/screens
|
||||
[24]:https://twitter.com/clarkis117
|
||||
[25]:https://twitter.com/shanselman
|
||||
[26]:https://twitter.com/shanselman/status/899005786826788865
|
||||
[27]:https://github.com/kataras/iris/blob/master/_benchmarks/screens/5m_requests_netcore-mvc.png
|
||||
[28]:http://localhost:5000/
|
||||
[29]:http://localhost:5000/
|
||||
[30]:http://localhost:5000/
|
||||
[31]:http://localhost:5000/
|
||||
[32]:https://github.com/kataras/iris/tree/master/_benchmarks
|
||||
[33]:https://github.com/kataras/iris
|
||||
[34]:https://dev.to/kataras/go-vsnet-core-in-terms-of-http-performance
|
||||
[35]:https://medium.com/@kataras/iris-go-vs-net-core-kestrel-in-terms-of-http-performance-806195dc93d5
|
||||
@@ -0,0 +1,75 @@
|
||||
编排工具充分发挥了 Linux 容器技术优势
|
||||
============================================================
|
||||
|
||||
> 一旦公司越过了“让我们看看这些容器如何工作”的阶段,他们最终会在许多不同的地方运行容器
|
||||
|
||||

|
||||
|
||||
需要快速、高效地交付程序的公司 —— 而今天,哪些公司不需要这样做?—— 是那些正在转向 Linux 容器的公司。他们还发现,一旦公司越过了“让我们看看这些容器如何工作”的阶段,他们最终会在许多不同的地方运行容器。
|
||||
|
||||
Linux 容器技术不是新技术,但它随着最初由 Docker 发明的创新性打包格式(现在的 [OCI][3] 格式)以及新应用对持续开发和部署的需求开始变得流行。在 Red Hat 的 2016 年 5 月的 Forrester 研究中,有 48% 的受访者表示已经在开发中使用容器,今年的数字预计将达到 53%。只有五分之一的受访者表示,他们在 2017 年不会在开发过程中利用容器。
|
||||
|
||||
像乐高积木一样,容器镜像可以轻松地重用代码和服务。每个容器镜像就像一个单独的、旨在做好一部分工作的乐高积木。它可能是数据库、数据存储、甚至预订服务或分析服务。通过单独包装每个组件,从而可以在不同的应用中使用。但是,如果没有某种程序定义(即<ruby>指令手册<rt>instruction booklet</rt></ruby>),则难以在不同环境中创建完整应用程序的副本。那就是容器编排的来由。
|
||||
|
||||

|
||||
|
||||
容器编排提供了像乐高系统这样的基础设施 —— 开发人员可以提供如何构建应用程序的简单说明。编排引擎将知道如何运行它。这使得可以轻松创建同一应用程序的多个副本,跨越开发人员电脑、CI/CD 系统,甚至生产数据中心和云提供商环境。
|
||||
|
||||
Linux 容器镜像允许公司在整个运行时环境(操作系统部件)中打包和隔离应用程序的构建块。在此基础上,通过容器编排,可以很容易地定义并运行所有的块,并一起构成完整的应用程序。一旦定义了完整的应用程序,它们就可以在不同的环境(开发、测试、生产等)之间移动,而不会破坏它们,且不改变它们的行为。
|
||||
|
||||
### 仔细调查容器
|
||||
|
||||
很明显,容器是有意义的,越来越多的公司像“对轮胎踹两脚”一样去研究容器。一开始,可能是一个开发人员使用一个容器工作,或是一组开发人员在使用多个容器。在后一种情况下,开发人员可能会随手编写一些代码来处理在容器部署超出单个实例之后快速出现的复杂性。
|
||||
|
||||
这一切都很好,毕竟他们是开发人员 —— 他们已经做到了。但即使在开发人员世界也会变得混乱,而且随手代码模式也没法跟着容器进入 QA 和生产环境下。
|
||||
|
||||
编排工具基本上做了两件事。首先,它们帮助开发人员定义他们的应用程序的表现 —— 一组用来构建应用程序实例的服务 —— 数据库、数据存储、Web 服务等。编排器帮助标准化应用程序的所有部分,在一起运行并彼此通信,我将这称之为标准化程序定义。其次,它们管理一个计算资源集群中启动、停止、升级和运行多个容器的过程,这在运行任何给定应用程序的多个副本时特别有用,例如持续集成 (CI) 和连续交付 (CD)。
|
||||
|
||||
想像一个公寓楼。居住在那里的每个人都有相同的街道地址,但每个人都有一个数字或字母或两者的组合,专门用来识别他或她。这是必要的,就像将正确的邮件和包裹交付给合适的租户一样。
|
||||
|
||||
同样,在容器中,只要你有两个容器或两个要运行这些容器的主机,你必须跟踪开发人员测试数据库连接或用户连接到正在运行的服务的位置。容器编排工具实质上有助于管理跨多个主机的容器的后勤。它们将生命周期管理功能扩展到由多个容器组成的完整应用程序,部署在一组机器上,从而允许用户将整个集群视为单个部署目标。
|
||||
|
||||
这真的很简单,又很复杂。编排工具提供了许多功能,从配置容器到识别和重新调度故障容器,将容器暴露给集群外的系统和服务,根据需要添加和删除容器等等。
|
||||
|
||||
虽然容器技术已经存在了一段时间,但容器编排工具只出现了几年。编排工具是 Google 从内部的高性能计算(HPC)和应用程序管理中吸取的经验教训开发的。在本质上,其要解决的就是在一堆服务器上运行一堆东西(批处理作业、服务等)。从那时起,编排工具已经进化到可以使公司能够战略性地利用容器。
|
||||
|
||||
一旦你的公司确定需要容器编排,下一步就是确定哪个平台对于业务是最有意义的。在评估容器编排时,请仔细查看(尤其):
|
||||
|
||||
* 应用程序定义语言
|
||||
* 现有能力集
|
||||
* 添加新功能的速度
|
||||
* 开源还是专有
|
||||
* 社区健康度(成员的积极性/高效,成员提交的质量/数量,贡献者的个人和公司的多样性)
|
||||
* 强化努力
|
||||
* 参考架构
|
||||
* 认证
|
||||
* 产品化过程
|
||||
|
||||
有三个主要的容器编排平台,它们似乎领先于其他,每个都有自己的历史。
|
||||
|
||||
1. **Docker Swarm:** Swarm 是容器典范 Docker 的附件。Swarm 允许用户建立并管理 Docker 节点的集群为单个虚拟系统。Swarm 似乎正在成为一个单一供应商的项目。
|
||||
2. **Mesos:** Mesos 是从 Apache 和高性能计算中成长起来的,因此是一个优秀的调度员。Mesos 的技术也非常先进,虽然与其他相比似乎没有发展速度或投资优势。
|
||||
3. **Kubernetes:** 由 Google 开发,由其内部编排工具 Borg 经验而来,Kubernetes 被广泛使用,并拥有强大的社区。其实这是 GitHub 上排名第一的项目。Mesos 目前可能比 Kubernetes 略有技术优势,但是 Kubernetes 是一个快速发展的项目,这也是为了长期技术上的收益而进行的架构投资。在不久的将来,在技术能力上应该能赶超 Mesos。
|
||||
|
||||
### 编排的未来
|
||||
|
||||
展望未来,企业们可以期待看到编排工具在应用程序和服务为中心的方向上发展。因为在现实中,如今快速应用程序开发实际上是在快速地利用服务、代码和数据的组合。无论这些服务是开源的,还是由内部团队部署的抑或从云提供商处购买的,未来将会是两者的混合。由于今天的编排器也在处理应用程序定义方面的挑战,所以期望看到它们越来越多地应对外部服务的整合。
|
||||
|
||||
此时此刻,想要充分利用容器的公司必须利用容器编排。
|
||||
|
||||
(题图:Thinkstock)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.infoworld.com/article/3205304/containers/orchestration-tools-enable-companies-to-fully-exploit-linux-container-technology.html
|
||||
|
||||
作者:[Scott McCarty][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.infoworld.com/author/Scott-McCarty/
|
||||
[1]:https://www.infoworld.com/article/3204171/what-is-docker-linux-containers-explained.html#tk.ifw-infsb
|
||||
[2]:https://www.infoworld.com/resources/16373/application-virtualization/the-beginners-guide-to-docker.html#tk.ifw-infsb
|
||||
[3]:https://github.com/opencontainers/image-spec
|
||||
@@ -0,0 +1,394 @@
|
||||
使用 Docker 构建你的 Serverless 树莓派集群
|
||||
============================================================
|
||||
|
||||
这篇博文将向你展示如何使用 Docker 和 [OpenFaaS][33] 框架构建你自己的 Serverless 树莓派集群。大家常常问我能用他们的集群来做些什么?而这个应用完美匹配卡片尺寸的设备——只需添加更多的树莓派就能获取更强的计算能力。
|
||||
|
||||
> “Serverless” (无服务器)是事件驱动架构的一种设计模式,与“桥接模式”、“外观模式”、“工厂模式”和“云”这些名词一样,都是一种抽象概念。
|
||||
|
||||

|
||||
|
||||
_图片:3 个 Raspberry Pi Zero_
|
||||
|
||||
这是我在本文中描述的集群,用黄铜支架分隔每个设备。
|
||||
|
||||
### Serverless 是什么?它为何重要?
|
||||
|
||||
行业对于 “serverless” 这个术语的含义有几种解释。在这篇博文中,我们就把它理解为一种事件驱动的架构模式,它能让你用自己喜欢的任何语言编写轻量可复用的功能。[更多关于 Serverless 的资料][22]。
|
||||
|
||||

|
||||
|
||||
_Serverless 架构也引出了“功能即服务服务”模式,简称 FaaS_
|
||||
|
||||
Serverless 的“功能”可以做任何事,但通常用于处理给定的输入——例如来自 GitHub、Twitter、PayPal、Slack、Jenkins CI pipeline 的事件;或者以树莓派为例,处理像红外运动传感器、激光绊网、温度计等真实世界的传感器的输入。
|
||||
|
||||

|
||||
|
||||
Serverless 功能能够更好地结合第三方的后端服务,使系统整体的能力大于各部分之和。
|
||||
|
||||
了解更多背景信息,可以阅读我最近一偏博文:[功能即服务(FaaS)简介][34]。
|
||||
|
||||
### 概述
|
||||
|
||||
我们将使用 [OpenFaaS][35],它能够让主机或者集群作为支撑 Serverless 功能运行的后端。任何能够使用 Docker 部署的可执行二进制文件、脚本或者编程语言都能在 [OpenFaaS][36] 上运作,你可以根据速度和伸缩性选择部署的规模。另一个优点是,它还内建了用户界面和监控系统。
|
||||
|
||||
这是我们要执行的步骤:
|
||||
|
||||
* 在一个或多个主机上配置 Docker (树莓派 2 或者 3);
|
||||
* 利用 Docker Swarm 将它们连接;
|
||||
* 部署 [OpenFaaS][23];
|
||||
* 使用 Python 编写我们的第一个功能。
|
||||
|
||||
### Docker Swarm
|
||||
|
||||
Docker 是一项打包和部署应用的技术,支持集群上运行,有着安全的默认设置,而且在搭建集群时只需要一条命令。OpenFaaS 使用 Docker 和 Swarm 在你的可用树莓派上传递你的 Serverless 功能。
|
||||
|
||||
我推荐你在这个项目中使用带树莓派 2 或者 3,以太网交换机和[强大的 USB 多端口电源适配器][37]。
|
||||
|
||||
### 准备 Raspbian
|
||||
|
||||
把 [Raspbian Jessie Lite][38] 写入 SD 卡(8GB 容量就正常工作了,但还是推荐使用 16GB 的 SD 卡)。
|
||||
|
||||
_注意:不要下载成 Raspbian Stretch 了_
|
||||
|
||||
> 社区在努力让 Docker 支持 Raspbian Stretch,但是还未能做到完美运行。请从[树莓派基金会网站][24]下载 Jessie Lite 镜像。
|
||||
|
||||
我推荐使用 [Etcher.io][39] 烧写镜像。
|
||||
|
||||
> 在引导树莓派之前,你需要在引导分区创建名为 `ssh` 的空白文件。这样才能允许远程登录。
|
||||
|
||||
#### 接通电源,然后修改主机名
|
||||
|
||||
现在启动树莓派的电源并且使用 `ssh` 连接:
|
||||
|
||||
```
|
||||
$ ssh pi@raspberrypi.local
|
||||
|
||||
```
|
||||
|
||||
> 默认密码是 `raspberry`
|
||||
|
||||
使用 `raspi-config` 工具把主机名改为 `swarm-1` 或者类似的名字,然后重启。
|
||||
|
||||
当你到了这一步,你还可以把划分给 GPU (显卡)的内存设置为 16MB。
|
||||
|
||||
#### 现在安装 Docker
|
||||
|
||||
我们可以使用通用脚本来安装:
|
||||
|
||||
```
|
||||
$ curl -sSL https://get.docker.com | sh
|
||||
```
|
||||
|
||||
> 这个安装方式在将来可能会发生变化。如上文所说,你的系统需要是 Jessie,这样才能得到一个确定的配置。
|
||||
|
||||
你可能会看到类似下面的警告,不过你可以安全地忽略它并且成功安装上 Docker CE 17.05:
|
||||
|
||||
```
|
||||
WARNING: raspbian is no longer updated @ https://get.docker.com/
|
||||
Installing the legacy docker-engine package...
|
||||
```
|
||||
|
||||
之后,用下面这个命令确保你的用户帐号可以访问 Docker 客户端:
|
||||
|
||||
```
|
||||
$ usermod pi -aG docker
|
||||
```
|
||||
|
||||
> 如果你的用户名不是 `pi`,那就把它替换成你的用户名。
|
||||
|
||||
#### 修改默认密码
|
||||
|
||||
输入 `$sudo passwd pi`,然后设置一个新密码,请不要跳过这一步!
|
||||
|
||||
#### 重复以上步骤
|
||||
|
||||
现在为其它的树莓派重复上述步骤。
|
||||
|
||||
### 创建你的 Swarm 集群
|
||||
|
||||
登录你的第一个树莓派,然后输入下面的命令:
|
||||
|
||||
```
|
||||
$ docker swarm init
|
||||
Swarm initialized: current node (3ra7i5ldijsffjnmubmsfh767) is now a manager.
|
||||
|
||||
To add a worker to this swarm, run the following command:
|
||||
|
||||
docker swarm join \
|
||||
--token SWMTKN-1-496mv9itb7584pzcddzj4zvzzfltgud8k75rvujopw15n3ehzu-af445b08359golnzhncbdj9o3 \
|
||||
192.168.0.79:2377
|
||||
|
||||
```
|
||||
|
||||
你会看到它显示了一个口令,以及其它节点加入集群的命令。接下来使用 `ssh` 登录每个树莓派,运行这个加入集群的命令。
|
||||
|
||||
等待连接完成后,在第一个树莓派上查看集群的节点:
|
||||
|
||||
```
|
||||
$ docker node ls
|
||||
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
|
||||
3ra7i5ldijsffjnmubmsfh767 * swarm1 Ready Active Leader
|
||||
k9mom28s2kqxocfq1fo6ywu63 swarm3 Ready Active
|
||||
y2p089bs174vmrlx30gc77h4o swarm4 Ready Active
|
||||
|
||||
```
|
||||
|
||||
恭喜你!你现在拥有一个树莓派集群了!
|
||||
|
||||
#### 更多关于集群的内容
|
||||
|
||||
你可以看到三个节点启动运行。这时只有一个节点是集群管理者。如果我们的管理节点_死机_了,集群就进入了不可修复的状态。我们可以通过添加冗余的管理节点解决这个问题。而且它们依然会运行工作负载,除非你明确设置了让你的服务只运作在工作节点上。
|
||||
|
||||
要把一个工作节点升级为管理节点,只需要在其中一个管理节点上运行 `docker node promote <node_name>` 命令。
|
||||
|
||||
> 注意: Swarm 命令,例如 `docker service ls` 或者 `docker node ls` 只能在管理节点上运行。
|
||||
|
||||
想深入了解管理节点与工作节点如何保持一致性,可以查阅 [Docker Swarm 管理指南][40]。
|
||||
|
||||
### OpenFaaS
|
||||
|
||||
现在我们继续部署程序,让我们的集群能够运行 Serverless 功能。[OpenFaaS][41] 是一个利用 Docker 在任何硬件或者云上让任何进程或者容器成为一个 Serverless 功能的框架。因为 Docker 和 Golang 的可移植性,它也能很好地运行在树莓派上。
|
||||
|
||||

|
||||
|
||||
> 如果你支持 [OpenFaaS][41],希望你能 **星标** [OpenFaaS][25] 的 GitHub 仓库。
|
||||
|
||||
登录你的第一个树莓派(你运行 `docker swarm init` 的节点),然后部署这个项目:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/alexellis/faas/
|
||||
$ cd faas
|
||||
$ ./deploy_stack.armhf.sh
|
||||
Creating network func_functions
|
||||
Creating service func_gateway
|
||||
Creating service func_prometheus
|
||||
Creating service func_alertmanager
|
||||
Creating service func_nodeinfo
|
||||
Creating service func_markdown
|
||||
Creating service func_wordcount
|
||||
Creating service func_echoit
|
||||
|
||||
```
|
||||
|
||||
你的其它树莓派会收到 Docer Swarm 的指令,开始从网上拉取这个 Docker 镜像,并且解压到 SD 卡上。这些工作会分布到各个节点上,所以没有哪个节点产生过高的负载。
|
||||
|
||||
这个过程会持续几分钟,你可以用下面指令查看它的完成状况:
|
||||
|
||||
```
|
||||
$ watch 'docker service ls'
|
||||
ID NAME MODE REPLICAS IMAGE PORTS
|
||||
57ine9c10xhp func_wordcount replicated 1/1 functions/alpine:latest-armhf
|
||||
d979zipx1gld func_prometheus replicated 1/1 alexellis2/prometheus-armhf:1.5.2 *:9090->9090/tcp
|
||||
f9yvm0dddn47 func_echoit replicated 1/1 functions/alpine:latest-armhf
|
||||
lhbk1fc2lobq func_markdown replicated 1/1 functions/markdownrender:latest-armhf
|
||||
pj814yluzyyo func_alertmanager replicated 1/1 alexellis2/alertmanager-armhf:0.5.1 *:9093->9093/tcp
|
||||
q4bet4xs10pk func_gateway replicated 1/1 functions/gateway-armhf:0.6.0 *:8080->8080/tcp
|
||||
v9vsvx73pszz func_nodeinfo replicated 1/1 functions/nodeinfo:latest-armhf
|
||||
|
||||
```
|
||||
|
||||
我们希望看到每个服务都显示 “1/1”。
|
||||
|
||||
你可以根据服务名查看该服务被调度到哪个树莓派上:
|
||||
|
||||
```
|
||||
$ docker service ps func_markdown
|
||||
ID IMAGE NODE STATE
|
||||
func_markdown.1 functions/markdownrender:latest-armhf swarm4 Running
|
||||
```
|
||||
|
||||
状态一项应该显示 `Running`,如果它是 `Pending`,那么镜像可能还在下载中。
|
||||
|
||||
在这时,查看树莓派的 IP 地址,然后在浏览器中访问它的 8080 端口:
|
||||
|
||||
```
|
||||
$ ifconfig
|
||||
```
|
||||
|
||||
例如,如果你的 IP 地址是 192.168.0.100,那就访问 http://192.168.0.100:8080 。
|
||||
|
||||
这是你会看到 FaaS UI(也叫 API 网关)。这是你定义、测试、调用功能的地方。
|
||||
|
||||
点击名称为 “func_markdown” 的 Markdown 转换功能,输入一些 Markdown(这是 Wikipedia 用来组织内容的语言)文本。
|
||||
|
||||
然后点击 “invoke”。你会看到调用计数增加,屏幕下方显示功能调用的结果。
|
||||
|
||||

|
||||
|
||||
### 部署你的第一个 Serverless 功能:
|
||||
|
||||
这一节的内容已经有相关的教程,但是我们需要几个步骤来配置树莓派。
|
||||
|
||||
#### 获取 FaaS-CLI
|
||||
|
||||
```
|
||||
$ curl -sSL cli.openfaas.com | sudo sh
|
||||
armv7l
|
||||
Getting package https://github.com/alexellis/faas-cli/releases/download/0.4.5-b/faas-cli-armhf
|
||||
```
|
||||
|
||||
#### 下载样例
|
||||
|
||||
```
|
||||
$ git clone https://github.com/alexellis/faas-cli
|
||||
$ cd faas-cli
|
||||
```
|
||||
|
||||
#### 为树莓派修补样例模版
|
||||
|
||||
我们临时修改我们的模版,让它们能在树莓派上工作:
|
||||
|
||||
```
|
||||
$ cp template/node-armhf/Dockerfile template/node/
|
||||
$ cp template/python-armhf/Dockerfile template/python/
|
||||
```
|
||||
|
||||
这么做是因为树莓派和我们平时关注的大多数计算机使用不一样的处理器架构。
|
||||
|
||||
> 了解 Docker 在树莓派上的最新状况,请查阅: [你需要了解的五件事][26]。
|
||||
|
||||
现在你可以跟着下面为 PC、笔记本和云端所写的教程操作,但我们在树莓派上要先运行一些命令。
|
||||
|
||||
* [使用 OpenFaaS 运行你的第一个 Serverless Python 功能][27]
|
||||
|
||||
注意第 3 步:
|
||||
|
||||
* 把你的功能放到先前从 GitHub 下载的 `faas-cli` 文件夹中,而不是 `~/functinos/hello-python` 里。
|
||||
* 同时,在 `stack.yml` 文件中把 `localhost` 替换成第一个树莓派的 IP 地址。
|
||||
|
||||
集群可能会花费几分钟把 Serverless 功能下载到相关的树莓派上。你可以用下面的命令查看你的服务,确保副本一项显示 “1/1”:
|
||||
|
||||
```
|
||||
$ watch 'docker service ls'
|
||||
pv27thj5lftz hello-python replicated 1/1 alexellis2/faas-hello-python-armhf:latest
|
||||
```
|
||||
|
||||
**继续阅读教程:** [使用 OpenFaaS 运行你的第一个 Serverless Python 功能][43]
|
||||
|
||||
关于 Node.js 或者其它语言的更多信息,可以进一步访问 [FaaS 仓库][44]。
|
||||
|
||||
### 检查功能的指标
|
||||
|
||||
既然使用 Serverless,你也不想花时间监控你的功能。幸运的是,OpenFaaS 内建了 [Prometheus][45] 指标检测,这意味着你可以追踪每个功能的运行时长和调用频率。
|
||||
|
||||
#### 指标驱动自动伸缩
|
||||
|
||||
如果你给一个功能生成足够的负载,OpenFaaS 将自动扩展你的功能;当需求消失时,你又会回到单一副本的状态。
|
||||
|
||||
这个请求样例你可以复制到浏览器中:
|
||||
|
||||
只要把 IP 地址改成你的即可。
|
||||
|
||||

|
||||
|
||||
```
|
||||
http://192.168.0.25:9090/graph?g0.range_input=15m&g0.stacked=1&g0.expr=rate(gateway_function_invocation_total%5B20s%5D)&g0.tab=0&g1.range_input=1h&g1.expr=gateway_service_count&g1.tab=0
|
||||
```
|
||||
|
||||
这些请求使用 PromQL(Prometheus 请求语言)编写。第一个请求返回功能调用的频率:
|
||||
|
||||
```
|
||||
rate(gateway_function_invocation_total[20s])
|
||||
```
|
||||
|
||||
第二个请求显示每个功能的副本数量,最开始应该是每个功能只有一个副本。
|
||||
|
||||
```
|
||||
gateway_service_count
|
||||
```
|
||||
|
||||
如果你想触发自动扩展,你可以在树莓派上尝试下面指令:
|
||||
|
||||
```
|
||||
$ while [ true ]; do curl -4 localhost:8080/function/func_echoit --data "hello world" ; done
|
||||
```
|
||||
|
||||
查看 Prometheus 的 “alerts” 页面,可以知道你是否产生足够的负载来触发自动扩展。如果没有,你可以尝试在多个终端同时运行上面的指令。
|
||||
|
||||

|
||||
|
||||
当你降低负载,副本数量显示在你的第二个图表中,并且 `gateway_service_count` 指标再次降回 1。
|
||||
|
||||
### 结束演讲
|
||||
|
||||
我们现在配置好了 Docker、Swarm, 并且让 OpenFaaS 运行代码,把树莓派像大型计算机一样使用。
|
||||
|
||||
> 希望大家支持这个项目,**星标** [FaaS 的 GitHub 仓库][28]。
|
||||
|
||||
你是如何搭建好了自己的 Docker Swarm 集群并且运行 OpenFaaS 的呢?在 Twitter [@alexellisuk][46] 上分享你的照片或推文吧。
|
||||
|
||||
**观看我在 Dockercon 上关于 OpenFaaS 的视频**
|
||||
|
||||
我在 [Austin 的 Dockercon][47] 上展示了 OpenFaaS。——观看介绍和互动例子的视频: https://www.youtube.com/embed/-h2VTE9WnZs
|
||||
|
||||
有问题?在下面的评论中提出,或者给我发邮件,邀请我进入你和志同道合者讨论树莓派、Docker、Serverless 的 Slack channel。
|
||||
|
||||
**想要学习更多关于树莓派上运行 Docker 的内容?**
|
||||
|
||||
我建议从 [你需要了解的五件事][48] 开始,它包含了安全性、树莓派和普通 PC 间微妙差别等话题。
|
||||
|
||||
* [Dockercon tips: Docker & Raspberry Pi][18]
|
||||
* [Control GPIO with Docker Swarm][19]
|
||||
* [Is that a Docker Engine in your pocket??][20]
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://blog.alexellis.io/your-serverless-raspberry-pi-cluster/
|
||||
|
||||
作者:[Alex Ellis][a]
|
||||
译者:[haoqixu](https://github.com/haoqixu)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://twitter.com/alexellisuk
|
||||
[1]:https://twitter.com/alexellisuk
|
||||
[2]:https://twitter.com/intent/tweet?in_reply_to=898978596773138436
|
||||
[3]:https://twitter.com/intent/retweet?tweet_id=898978596773138436
|
||||
[4]:https://twitter.com/intent/like?tweet_id=898978596773138436
|
||||
[5]:https://twitter.com/alexellisuk
|
||||
[6]:https://twitter.com/alexellisuk
|
||||
[7]:https://twitter.com/Docker
|
||||
[8]:https://twitter.com/Raspberry_Pi
|
||||
[9]:https://twitter.com/alexellisuk/status/898978596773138436
|
||||
[10]:https://twitter.com/alexellisuk/status/899545370916728832/photo/1
|
||||
[11]:https://twitter.com/alexellisuk
|
||||
[12]:https://twitter.com/alexellisuk/status/898978596773138436/photo/1
|
||||
[13]:https://twitter.com/alexellisuk/status/898978596773138436/photo/1
|
||||
[14]:https://twitter.com/alexellisuk/status/898978596773138436/photo/1
|
||||
[15]:https://twitter.com/alexellisuk/status/898978596773138436/photo/1
|
||||
[16]:https://twitter.com/alexellisuk/status/899545370916728832/photo/1
|
||||
[17]:https://support.twitter.com/articles/20175256
|
||||
[18]:https://blog.alexellis.io/dockercon-tips-docker-raspberry-pi/
|
||||
[19]:https://blog.alexellis.io/gpio-on-swarm/
|
||||
[20]:https://blog.alexellis.io/docker-engine-in-your-pocket/
|
||||
[21]:https://news.ycombinator.com/item?id=15052192
|
||||
[22]:https://blog.alexellis.io/introducing-functions-as-a-service/
|
||||
[23]:https://github.com/alexellis/faas
|
||||
[24]:http://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2017-07-05/
|
||||
[25]:https://github.com/alexellis/faas
|
||||
[26]:https://blog.alexellis.io/5-things-docker-rpi/
|
||||
[27]:https://blog.alexellis.io/first-faas-python-function
|
||||
[28]:https://github.com/alexellis/faas
|
||||
[29]:https://blog.alexellis.io/tag/docker/
|
||||
[30]:https://blog.alexellis.io/tag/raspberry-pi/
|
||||
[31]:https://blog.alexellis.io/tag/openfaas/
|
||||
[32]:https://blog.alexellis.io/tag/faas/
|
||||
[33]:https://github.com/alexellis/faas
|
||||
[34]:https://blog.alexellis.io/introducing-functions-as-a-service/
|
||||
[35]:https://github.com/alexellis/faas
|
||||
[36]:https://github.com/alexellis/faas
|
||||
[37]:https://www.amazon.co.uk/Anker-PowerPort-Family-Sized-Technology-Smartphones/dp/B00PK1IIJY
|
||||
[38]:http://downloads.raspberrypi.org/raspbian/images/raspbian-2017-07-05/
|
||||
[39]:https://etcher.io/
|
||||
[40]:https://docs.docker.com/engine/swarm/admin_guide/
|
||||
[41]:https://github.com/alexellis/faas
|
||||
[42]:http://192.168.0.100:8080/
|
||||
[43]:https://blog.alexellis.io/first-faas-python-function
|
||||
[44]:https://github.com/alexellis/faas
|
||||
[45]:https://prometheus.io/
|
||||
[46]:https://twitter.com/alexellisuk
|
||||
[47]:https://blog.alexellis.io/dockercon-2017-captains-log/
|
||||
[48]:https://blog.alexellis.io/5-things-docker-rpi/
|
||||
124
published/201710/20170903 Genymotion vs Android Emulator.md
Normal file
124
published/201710/20170903 Genymotion vs Android Emulator.md
Normal file
@@ -0,0 +1,124 @@
|
||||
Genymotion vs Android 模拟器
|
||||
============================================================
|
||||
|
||||
> Android 模拟器是否改善到足以取代 Genymotion
|
||||
|
||||
一直以来有关于选择 android 模拟器或者 Genymotion 的争论,我看到很多讨论最后以赞成 Genymotion 而告终。我根据我周围最常见的情况收集了一些数据,基于此,我将连同 Genymotion 全面评估 android 模拟器。
|
||||
|
||||
结论剧透:配置正确时,Android 模拟器比 Genymotion 快。
|
||||
|
||||
使用带 Google API 的 x86(32位)镜像、3GB RAM、四核CPU。
|
||||
|
||||
> - 哈,很高兴我们知道了最终结果
|
||||
> - 现在,让我们深入
|
||||
|
||||
免责声明:我已经测试了我看到的一般情况,即运行测试。所有的基准测试都是在 2015 年中期的 MacBook Pro 上完成的。无论何时我提及 Genymotion 指的都是 Genymotion Desktop。他们还有其他产品,如 Genymotion on Cloud&Genymotion on Demand,但这里没有考虑。我不是说 Genymotion 是不合适的,但运行测试比某些 Android 模拟器慢。
|
||||
|
||||
关于这个问题的一点背景,然后我们将转到具体内容上去。
|
||||
|
||||
_过去:我有一些基准测试,继续下去。_
|
||||
|
||||
很久以前,Android 模拟器是唯一的选择。但是它们太慢了,这是架构改变的原因。对于在 x86 机器上运行的 ARM 模拟器,你能期待什么?每个指令都必须从 ARM 转换为 x86 架构,这使得它的速度非常慢。
|
||||
|
||||
随之而来的是 Android 的 x86 镜像,随着它们摆脱了 ARM 到 x86 平台转化,速度更快了。现在,你可以在 x86 机器上运行 x86 Android 模拟器。
|
||||
|
||||
> - _问题解决了!!!_
|
||||
> - 没有!
|
||||
|
||||
Android 模拟器仍然比人们想要的慢。随后出现了 Genymotion,这是一个在 virtual box 中运行的 Android 虚拟机。与在 qemu 上运行的普通老式 android 模拟器相比,它相当稳定和快速。
|
||||
|
||||
我们来看看今天的情况。
|
||||
|
||||
我在持续集成的基础设施上和我的开发机器上使用 Genymotion。我手头的任务是摆脱持续集成基础设施和开发机器上使用 Genymotion。
|
||||
|
||||
> - 你问为什么?
|
||||
> - 授权费钱。
|
||||
|
||||
在快速看了一下以后,这似乎是一个愚蠢的举动,因为 Android 模拟器的速度很慢而且有 bug,它们看起来适得其反,但是当你深入的时候,你会发现 Android 模拟器是优越的。
|
||||
|
||||
我们的情况是对它们进行集成测试(主要是 espresso)。我们的应用程序中只有 1100 多个测试,Genymotion 需要大约 23 分钟才能运行所有测试。
|
||||
|
||||
在 Genymotion 中我们面临的另一些问题是:
|
||||
|
||||
* 有限的命令行工具([GMTool][1])。
|
||||
* 由于内存问题,它们需要定期重新启动。这是一个手动任务,想象在配有许多机器的持续集成基础设施上进行这些会怎样。
|
||||
|
||||
**进入 Android 模拟器**
|
||||
|
||||
首先是尝试在它给你这么多的选择中设置一个,这会让你会觉得你在赛百味餐厅一样。最大的问题是 x86 或 x86_64 以及是否有 Google API。
|
||||
|
||||
我用这些组合做了一些研究和基准测试,这是我们所想到的。
|
||||
|
||||
鼓声……
|
||||
|
||||
> - 比赛的获胜者是带 Google API 的 x86
|
||||
> - 但是如何胜利的?为什么?
|
||||
|
||||
嗯,我会告诉你每一个问题。
|
||||
|
||||
x86_64 比 x86 慢
|
||||
|
||||
> - 你问慢多少。
|
||||
> - 28.2% 多!!!
|
||||
|
||||
使用 Google API 的模拟器更加稳定,没有它们容易崩溃。
|
||||
|
||||
这使我们得出结论:最好的是带 Google API 的x86。
|
||||
|
||||
在我们抛弃 Genymotion 开始使用模拟器之前。有下面几点重要的细节。
|
||||
|
||||
* 我使用的是带 Google API 的 Nexus 5 镜像。
|
||||
* 我注意到,给模拟器较少的内存会造成了很多 Google API 崩溃。所以为模拟器设定了 3GB 的 RAM。
|
||||
* 模拟器有四核。
|
||||
* HAXM 安装在主机上。
|
||||
|
||||
**基准测试的时候到了**
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
从基准测试上你可以看到除了 Geekbench4,Android 模拟器都击败了 Genymotion,我感觉更像是virtual box 击败了 qemu。
|
||||
|
||||
> 欢呼模拟器之王
|
||||
|
||||
我们现在有更快的测试执行时间、更好的命令行工具。最新的 [Android 模拟器][2]创下的新的记录。更快的启动时间之类。
|
||||
|
||||
Goolgle 一直努力让
|
||||
|
||||
> Android 模拟器变得更好
|
||||
|
||||
如果你没有在使用 Android 模拟器。我建议你重新试下,可以节省一些钱。
|
||||
|
||||
我尝试的另一个但是没有成功的方案是在 AWS 上运行 [Android-x86][3] 镜像。我能够在 vSphere ESXi Hypervisor 中运行它,但不能在 AWS 或任何其他云平台上运行它。如果有人知道原因,请在下面评论。
|
||||
|
||||
PS:[VMWare 现在可以在 AWS 上使用][4],在 AWS 上使用 [Android-x86][5] 毕竟是有可能的。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
嗨,我的名字是 Sumit Gupta。我是来自印度古尔冈的软件/应用/网页开发人员。我做这个是因为我喜欢技术,并且一直迷恋它。我已经工作了 3 年以上,但我还是有很多要学习。他们不是说如果你有知识,让别人点亮他们的蜡烛。
|
||||
|
||||
当在编译时,我阅读很多文章,或者听音乐。
|
||||
|
||||
如果你想联系,下面是我的社交信息和 [email][6]。
|
||||
|
||||
----
|
||||
via: https://www.plightofbyte.com/android/2017/09/03/genymotion-vs-android-emulator/
|
||||
|
||||
作者:[Sumit Gupta][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.plightofbyte.com/about-me
|
||||
[1]:https://docs.genymotion.com/Content/04_Tools/GMTool/GMTool.htm
|
||||
[2]:https://developer.android.com/studio/releases/emulator.html
|
||||
[3]:http://www.android-x86.org/
|
||||
[4]:https://aws.amazon.com/vmware/
|
||||
[5]:http://www.android-x86.org/
|
||||
[6]:thesumitgupta@outlook.com
|
||||
@@ -0,0 +1,104 @@
|
||||
关于 Linux 你可能不是非常了解的七件事
|
||||
===
|
||||
|
||||

|
||||
|
||||
使用 Linux 最酷的事情之一就是随着时间的推移,你可以不断获得新的知识。每天,你都可能会遇到一个新的实用工具,或者只是一个不太熟悉的奇技淫巧,但是却非常有用。这些零碎的东西并不总是能够改变生活,但是却是专业知识的基础。
|
||||
|
||||
即使是专家,也不可能事事皆知。无论你有多少经验,可能总会有更多的东西需要你去学习。所以,在这儿我列出了七件关于 Linux 你可能不知道的事情。
|
||||
|
||||
### 一个查找命令历史的交互模式
|
||||
|
||||
你可能对 `history` 命令非常熟悉,它会读取 bash 历史,然后以编号列表的方式输出到标准输出(`stdout`)。然而,如果你在 `curl` 命令的海洋里寻找一个特定的链接(URL),那么这个列表并不总是那么容易阅读的。
|
||||
|
||||
你还可以有另一个选择,Linux 有一个交互式的反向搜索可以帮助你解决这个问题。你可以通过快捷键 `ctrl+r`启动交互模式,然后进入一个交互提示中,它将会根据你提供的字符串来向后搜索 bash 历史,你可以通过再次按下 `ctrl+r` 向后搜索更老的命令,或者按下 `ctrl+s` 向前搜索。
|
||||
|
||||
注意,`ctrl+s` 有时会与 XON/XOFF 流控制冲突,即 XON/XOFF 流控制也会使用该快捷键。你可以通过运行 `stty -ixon` 命令来禁用该快捷键。在你的个人电脑上,这通常是有用的,但是在禁用前,确保你不需要 XON/XOFF 。
|
||||
|
||||
### Cron 不是安排任务的唯一方式
|
||||
|
||||
Cron 任务对于任何水平的系统管理员,无论是毫无经验的初学者,还是经验丰富的专家来说,都是非常有用的。但是,如果你需要安排一个一次性的任务,那么 `at` 命令为你提供了一个快捷的方式来创建任务,从而你不需要接触 crontab 。
|
||||
|
||||
`at` 命令的运行方式是在后面紧跟着你想要运行任务的运行时间。时间是灵活的,因为它支持许多时间格式。包括下面这些例子:
|
||||
|
||||
```
|
||||
at 12:00 PM September 30 2017
|
||||
at now + 1 hour
|
||||
at 9:00 AM tomorrow
|
||||
```
|
||||
|
||||
当你以带参数的方式输入 `at` 命令以后,将会提示你该命令将在你的 Linux 系统上运行。这可能是一个备份脚本,一套维护任务,或者甚至是一个普通的 bash 命令。如果要结束任务,可以按 `ctrl+d` 。
|
||||
|
||||
另外,你可以使用 `atq` 命令查看当前用户的所有任务,或者使用 `sudo atq` 查看所有用户的任务。它将会展示出所有排定好的任务,并且每个任务都伴有一个 ID 。如果你想取消一个排定好的任务,可以使用 `atrm` 命令,并且以任务 ID 作为参数。
|
||||
|
||||
### 你可以按照功能搜索命令,而不仅仅是通过名字
|
||||
|
||||
记住命令的名字非常困难,特别是对于初学者来说。幸运的是,Linux 附带了一个通过名字和描述来搜索 man 页面的工具。
|
||||
|
||||
下次,如果你没有记住你想要使用的工具的名称,你可以尝试使用 `apropos` 命令加上你想要干的事情的描述。比如,`apropos build filesystem` 将会返回一系列名字和描述包括了 “build” 和 “filesystem” 单词的工具。
|
||||
|
||||
`apropos` 命令接受一个或多个字符串作为参数,但同时它也有其他参数,比如你可以使用 `-r` 参数,从而通过正则表达式来搜索。
|
||||
|
||||
### 一个允许你来管理系统版本的替代系统
|
||||
|
||||
如果你曾进行过软件开发,你就会明白跨项目管理不同版本的语言的支持的重要性。许多 Linux 发行版都有工具可以来处理不同的内建版本。
|
||||
|
||||
可执行文件比如 `java` 往往符号链接到目录 `/etc/alternatives` 下。反过来,该目录会将符号链接存储为二进制文件并提供一个管理这些链接的接口。Java 可能是替代系统最常管理的语言,但是,经过一些配置,它也可以作为其他应用程序替代品,比如 NVM 和 RVM (NVM 和 RVM 分别是 NodeJS 和 Ruby 的版本管理器)。
|
||||
|
||||
在基于 Debian 的系统中,你可以使用 `update-alternatives` 命令创建和管理这些链接。在 CentOS 中,这个工具就叫做 `alternatives` 。通过更改你的 alternatives 文件中的链接,你便可以安装一个语言的多个版本,并且在不同的情况下使用不同的二进制。这个替代系统也提供了对任何你可能在命令行运行的程序的支持。
|
||||
|
||||
### `shred` 命令是更加安全的删除文件方式
|
||||
|
||||
我们大多数时候总是使用 `rm` 命令来删除文件。但是文件去哪儿了呢?真相是 `rm` 命令所做的事情并不是像你所想像的那样,它仅仅删除了文件系统和硬盘上的数据的硬链接。硬盘上的数据依旧存在,直到被另一个应用重写覆盖。对于非常敏感的数据来说,这会带来一个很大的安全隐患。
|
||||
|
||||
`shred` 命令是 `rm` 命令的升级版。当你使用 `shred` 命令删除一个文件之后,文件中的数据会被多次随机覆写。甚至有一个选项可以在随机覆写之后对所有的数据进行清零。
|
||||
|
||||
如果你想安全的删除一个文件并且以零覆盖,那么可以使用下面的命令:
|
||||
|
||||
`shred -u -z [file name]`
|
||||
|
||||
同时,你也可以使用 `-n` 选项和一个数字作为参数,从而指定在随机覆盖数据的时候迭代多少次。
|
||||
|
||||
### 通过自动更正来避免输入很长的无效文件路径
|
||||
|
||||
有多少次,你输入一个文件的绝对路径,然而却看到“没有该文件或目录”的消息。任何人都会明白输入一个很长的字符串的痛苦。幸运的是,有一个很简单的解决办法。
|
||||
|
||||
内建的 `shopt` 命令允许你设置不同的选项来改变 shell 的行为。设置 `cdspell` 选项是避免输入文件路径时一个字母出错的头痛的一个简单方式。你可以通过运行 `shopt -s cdspell` 命令来启用该选项。启用该选项后,当你想要切换目录时,会自动更正为最匹配的目录。
|
||||
|
||||
Shell 选项是节省时间的一个好方法(更不用说减少麻烦),此外还有许许多多的其他选项。如果想查看你的系统中所有选项的完整列表,可以运行不带参数的 `shopt` 命令。需要注意的是,这是 bash 的特性,如果你运行 zsh 或者其他可供选择的 shell,可能无法使用。
|
||||
|
||||
### 通过子 shell 返回到当前目录
|
||||
|
||||
如果你曾经配置过一个比较复杂的系统,那么你可能会发现你需要频繁的更换目录,从而很难跟踪你所在的位置。如果在运行完一个命令后自动返回到当前位置,不是很好吗?
|
||||
|
||||
Linux 系统实际上提供了一个解决该问题的方法,并且非常简单。如果你想通过 `cd` 命令进入另一个目录完成一些任务,然后再返回当前工作目录,那么你可以将命令置于括号中。你可以在你的 Linux 系统上尝试下面这个命令。记住你当前的工作目录,然后运行:
|
||||
|
||||
```
|
||||
(cd /etc && ls -a)
|
||||
```
|
||||
|
||||
该命令会输出 `/etc` 目录的内容。现在,检查你的当前工作目录。它和执行该命令前的目录一样,而不是 `/etc` 目录。
|
||||
|
||||
它是如何工作的呢?运行一个括号中的命令会创建一个子 shell 或一个当前 shell 进程的复刻副本。该子 shell 可以访问所有的父变量,反之则不行。所以请记住,你是在运行一个非常复杂的单行命令。
|
||||
|
||||
在并行处理中经常使用子 shell ,但是在命令行中,它也能为你带来同样的力量,从而使你在浏览文件系统时更加容易。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
Phil Zona 是 Linux Academy 的技术作家。他编写了 AWS、Microsoft Azure 和 Linux 系统管理的指南和教程。他同时也管理着 Cloud Assessments 博客,该博客旨在帮助个人通过技术实现他们的事业目标。
|
||||
|
||||
-----------------
|
||||
|
||||
via: http://opensourceforu.com/2017/09/top-7-things-linux-may-not-known-far/
|
||||
|
||||
作者:[PHIL ZONA][a]
|
||||
译者:[ucasFL](https://github.com/ucasFL)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:http://opensourceforu.com/author/phil-zona/
|
||||
[1]:http://opensourceforu.com/2017/09/top-7-things-linux-may-not-known-far/#disqus_thread
|
||||
[2]:http://opensourceforu.com/author/phil-zona/
|
||||
@@ -0,0 +1,325 @@
|
||||
12 件可以用 GitHub 完成的很酷的事情
|
||||
============================================================
|
||||
|
||||
我不能为我的人生想出一个引子来,所以……
|
||||
|
||||
### #1 在 GitHub.com 上编辑代码
|
||||
|
||||
我想我要开始介绍的第一件事是多数人都已经知道的(尽管我一周之前还不知道)。
|
||||
|
||||
当你登录到 GitHub ,查看一个文件时(任何文本文件,任何版本库),右上方会有一只小铅笔。点击它,你就可以编辑文件了。 当你编辑完成后,GitHub 会给出文件变更的建议,然后为你<ruby>复刻<rt>fork</rt></ruby>该仓库并创建一个<ruby>拉取请求<rt>pull request</rt></ruby>(PR)。
|
||||
|
||||
是不是很疯狂?它为你创建了一个复刻!
|
||||
|
||||
你不需要自己去复刻、拉取,然后本地修改,再推送,然后创建一个 PR。
|
||||
|
||||

|
||||
|
||||
*不是一个真正的 PR*
|
||||
|
||||
这对于修改错误拼写以及编辑代码时的一些糟糕的想法是很有用的。
|
||||
|
||||
### #2 粘贴图像
|
||||
|
||||
在评论和<ruby>工单<rt>issue</rt></ruby>的描述中并不仅限于使用文字。你知道你可以直接从剪切板粘贴图像吗? 在你粘贴的时候,你会看到图片被上传 (到云端,这毫无疑问),并转换成 markdown 显示的图片格式。
|
||||
|
||||
棒极了。
|
||||
|
||||
### #3 格式化代码
|
||||
|
||||
如果你想写一个代码块的话,你可以用三个反引号(```)作为开始 —— 就像你在浏览 [精通 Markdown][3] 时所学到的一样 —— 而且 GitHub 会尝试去推测你所写下的编程语言。
|
||||
|
||||
但如果你粘贴的像是 Vue、Typescript 或 JSX 这样的代码,你就需要明确指出才能获得高亮显示。
|
||||
|
||||
在首行注明 ````jsx`:
|
||||
|
||||

|
||||
|
||||
…这意味着代码段已经正确的呈现:
|
||||
|
||||

|
||||
|
||||
(顺便说一下,这些用法也可以用到 gist。 如果你给一个 gist 用上 `.jsx` 扩展名,你的 JSX 语法就会高亮显示。)
|
||||
|
||||
这里是[所有被支持的语法][4]的清单。
|
||||
|
||||
### #4 用 PR 中的魔法词来关闭工单
|
||||
|
||||
比方说你已经创建了一个用来修复 `#234` 工单的拉取请求。那么你就可以把 `fixes #234` 这段文字放在你的 PR 的描述中(或者是在 PR 的评论的任何位置)。
|
||||
|
||||
接下来,在合并 PR 时会自动关闭与之对应的工单。这是不是很酷?
|
||||
|
||||
这里是[更详细的学习帮助][5]。
|
||||
|
||||
### #5 链接到评论
|
||||
|
||||
是否你曾经想要链接到一个特定的评论但却无从着手?这是因为你不知道如何去做到这些。不过那都过去了,我的朋友,我告诉你啊,点击紧挨着名字的日期或时间,这就是如何链接到一个评论的方法。
|
||||
|
||||

|
||||
|
||||
*嘿,这里有 gaearon 的照片!*
|
||||
|
||||
### #6 链接到代码
|
||||
|
||||
那么你想要链接到代码的特定行么。我了解了。
|
||||
|
||||
试试这个:在查看文件的时候,点击挨着代码的行号。
|
||||
|
||||
哇哦,你看到了么?URL 更新了,加上了行号!如果你按下 `Shift` 键并点击其他的行号,格里格里巴巴变!URL 再一次更新并且现在出现了行范围的高亮。
|
||||
|
||||
分享这个 URL 将会链接到这个文件的那些行。但等一下,链接所指向的是当前分支。如果文件发生变更了怎么办?也许一个文件当前状态的<ruby>永久链接<rt>permalink</rt></ruby>就是你以后需要的。
|
||||
|
||||
我比较懒,所以我已经在一张截图中做完了上面所有的步骤:
|
||||
|
||||

|
||||
|
||||
*说起 URL…*
|
||||
|
||||
### #7 像命令行一样使用 GitHub URL
|
||||
|
||||
使用 UI 来浏览 GitHub 有着很好的体验。但有些时候最快到达你想去的地方的方法就是在地址栏输入。举个例子,如果我想要跳转到一个我正在工作的分支,然后查看与 master 分支的差异,我就可以在我的仓库名称的后边输入 `/compare/branch-name` 。
|
||||
|
||||
这样就会访问到指定分支的 diff 页面。
|
||||
|
||||

|
||||
|
||||
然而这就是与 master 分支的 diff,如果我要与 develoment 分支比较,我可以输入 `/compare/development...my-branch`。
|
||||
|
||||

|
||||
|
||||
对于你这种键盘快枪手来说,`ctrl`+`L` 或 `cmd`+`L` 将会向上跳转光标进入 URL 那里(至少在 Chrome 中是这样)。这(再加上你的浏览器会自动补全)能够成为一种在分支间跳转的便捷方式。
|
||||
|
||||
专家技巧:使用方向键在 Chrome 的自动完成建议中移动同时按 `shift`+`delete` 来删除历史条目(例如,一旦分支被合并后)。
|
||||
|
||||
(我真的好奇如果我把快捷键写成 `shift + delete` 这样的话,是不是读起来会更加容易。但严格来说 ‘+’ 并不是快捷键的一部分,所以我并不觉得这很舒服。这一点纠结让 _我_ 整晚难以入睡,Rhonda。)
|
||||
|
||||
### #8 在工单中创建列表
|
||||
|
||||
你想要在你的<ruby>工单<rt>issue</rt></ruby>中看到一个复选框列表吗?
|
||||
|
||||

|
||||
|
||||
你想要在工单列表中显示为一个漂亮的 “2 of 5” 进度条吗?
|
||||
|
||||

|
||||
|
||||
很好!你可以使用这些的语法创建交互式的复选框:
|
||||
|
||||
```
|
||||
- [ ] Screen width (integer)
|
||||
- [x] Service worker support
|
||||
- [x] Fetch support
|
||||
- [ ] CSS flexbox support
|
||||
- [ ] Custom elements
|
||||
```
|
||||
|
||||
它的表示方法是空格、破折号、再空格、左括号、填入空格(或者一个 `x` ),然后封闭括号,接着空格,最后是一些话。
|
||||
|
||||
然后你可以实际选中或取消选中这些框!出于一些原因这些对我来说看上去就像是技术魔法。你可以_选中_这些框! 同时底层的文本会进行更新。
|
||||
|
||||
他们接下来会想到什么魔法?
|
||||
|
||||
噢,如果你在一个<ruby>项目面板<rt>project board</rt></ruby>上有这些工单的话,它也会在这里显示进度:
|
||||
|
||||

|
||||
|
||||
如果在我提到“在一个项目面板上”时你不知道我在说些什么,那么你会在本页下面进一步了解。
|
||||
|
||||
比如,在本页面下 2 厘米的地方。
|
||||
|
||||
### #9 GitHub 上的项目面板
|
||||
|
||||
我常常在大项目中使用 Jira 。而对于个人项目我总是会使用 Trello 。我很喜欢它们两个。
|
||||
|
||||
当我学会 GitHub 的几周后,它也有了自己的项目产品,就在我的仓库上的 Project 标签,我想我会照搬一套我已经在 Trello 上进行的任务。
|
||||
|
||||

|
||||
|
||||
*没有一个是有趣的任务*
|
||||
|
||||
这里是在 GitHub 项目上相同的内容:
|
||||
|
||||

|
||||
|
||||
*你的眼睛最终会适应这种没有对比的显示*
|
||||
|
||||
出于速度的缘故,我把上面所有的都添加为 “<ruby>备注<rt>note</rt></ruby>” —— 意思是它们不是真正的 GitHub 工单。
|
||||
|
||||
但在 GitHub 上,管理任务的能力被集成在版本库的其他地方 —— 所以你可能想要从仓库添加已有的工单到面板上。
|
||||
|
||||
你可以点击右上角的<ruby>添加卡片<rt>Add Cards</rt></ruby>,然后找你想要添加的东西。在这里,特殊的[搜索语法][6]就派上用场了,举个例子,输入 `is:pr is:open` 然后现在你可以拖动任何开启的 PR 到项目面板上,或者要是你想清理一些 bug 的话就输入 `label:bug`。
|
||||
|
||||

|
||||
|
||||
亦或者你可以将现有的备注转换为工单。
|
||||
|
||||

|
||||
|
||||
再或者,从一个现有工单的屏幕上,把它添加到右边面板的项目上。
|
||||
|
||||

|
||||
|
||||
它们将会进入那个项目面板的分类列表,这样你就能决定放到哪一类。
|
||||
|
||||
在实现那些任务的同一个仓库下放置任务的内容有一个巨大(超大)的好处。这意味着今后的几年你能够在一行代码上做一个 `git blame`,可以让你找出最初在这个任务背后写下那些代码的根据,而不需要在 Jira、Trello 或其它地方寻找蛛丝马迹。
|
||||
|
||||
#### 缺点
|
||||
|
||||
在过去的三周我已经对所有的任务使用 GitHub 取代 Jira 进行了测试(在有点看板风格的较小规模的项目上) ,到目前为止我都很喜欢。
|
||||
|
||||
但是我无法想象在 scrum(LCTT 译注:迭代式增量软件开发过程)项目上使用它,我想要在那里完成正确的工期估算、开发速度的测算以及所有的好东西怕是不行。
|
||||
|
||||
好消息是,GitHub 项目只有很少一些“功能”,并不会让你花很长时间去评估它是否值得让你去切换。因此要不要试试,你自己看着办。
|
||||
|
||||
无论如何,我_听说过_ [ZenHub][7] 并且在 10 分钟前第一次打开了它。它是对 GitHub 高效的延伸,可以让你估计你的工单并创建 epic 和 dependency。它也有 velocity 和<ruby>燃尽图<rt>burndown chart</rt></ruby>功能;这看起来_可能是_世界上最棒的东西了。
|
||||
|
||||
延伸阅读: [GitHub help on Projects][8]。
|
||||
|
||||
### #10 GitHub 维基
|
||||
|
||||
对于一堆非结构化页面(就像维基百科一样), GitHub <ruby>维基<rt>wiki</rt></ruby>提供的(下文我会称之为 Gwiki)就很优秀。
|
||||
|
||||
结构化的页面集合并没那么多,比如说你的文档。这里没办法说“这个页面是那个页面的子页”,或者有像‘下一节’和‘上一节’这样的按钮。Hansel 和 Gretel 将会完蛋,因为这里没有面包屑导航(LCTT 译注:引自童话故事《糖果屋》)。
|
||||
|
||||
(边注,你有_读过_那个故事吗? 这是个残酷的故事。两个混蛋小子将饥肠辘辘的老巫婆烧死在_她自己的火炉_里。毫无疑问她是留下来收拾残局的。我想这就是为什么如今的年轻人是如此的敏感 —— 今天的睡前故事太不暴力了。)
|
||||
|
||||
继续 —— 把 Gwiki 拿出来接着讲,我输入一些 NodeJS 文档中的内容作为维基页面,然后创建一个侧边栏以模拟一些真实结构。这个侧边栏会一直存在,尽管它无法高亮显示你当前所在的页面。
|
||||
|
||||
其中的链接必须手动维护,但总的来说,我认为这已经很好了。如果你觉得有需要的话可以[看一下][9]。
|
||||
|
||||

|
||||
|
||||
它将不会与像 GitBook(它使用了 [Redux 文档][10])或定制的网站这样的东西相比较。但它八成够用了,而且它就在你的仓库里。
|
||||
|
||||
我是它的一个粉丝。
|
||||
|
||||
我的建议:如果你已经拥有不止一个 `README.md` 文件,并且想要一些不同的页面作为用户指南或是更详细的文档,那么下一步你就需要停止使用 Gwiki 了。
|
||||
|
||||
如果你开始觉得缺少的结构或导航非常有必要的话,去切换到其他的产品吧。
|
||||
|
||||
### #11 GitHub 页面(带有 Jekyll)
|
||||
|
||||
你可能已经知道了可以使用 GitHub <ruby>页面<rt>Pages</rt></ruby> 来托管静态站点。如果你不知道的话现在就可以去试试。不过这一节确切的说是关于使用 Jekyll 来构建一个站点。
|
||||
|
||||
最简单的来说, GitHub 页面 + Jekyll 会将你的 `README.md` 呈现在一个漂亮的主题中。举个例子,看看我的 [关于 github][11] 中的 readme 页面:
|
||||
|
||||

|
||||
|
||||
点击 GitHub 上我的站点的<ruby>设置<rt>settings</rt></ruby>标签,开启 GitHub 页面功能,然后挑选一个 Jekyll 主题……
|
||||
|
||||

|
||||
|
||||
我就会得到一个 [Jekyll 主题的页面][12]:
|
||||
|
||||

|
||||
|
||||
由此我可以构建一个主要基于易于编辑的 markdown 文件的静态站点,其本质上是把 GitHub 变成一个 CMS(LCTT 译注:内容管理系统)。
|
||||
|
||||
我还没有真正的使用过它,但这就是 React 和 Bootstrap 网站构建的过程,所以并不可怕。
|
||||
|
||||
注意,在本地运行它需要 Ruby (Windows 用户会彼此交换一下眼神,然后转头看向其它的方向。macOS 用户会发出这样这样的声音 “出什么问题了,你要去哪里?Ruby 可是一个通用平台!GEMS 万岁!”)。
|
||||
|
||||
(这里也有必要加上,“暴力或威胁的内容或活动” 在 GitHub 页面上是不允许的,因此你不能去部署你的 Hansel 和 Gretel 重启之旅了。)
|
||||
|
||||
#### 我的意见
|
||||
|
||||
为了这篇文章,我对 GitHub 页面 + Jekyll 研究越多,就越觉得这件事情有点奇怪。
|
||||
|
||||
“拥有你自己的网站,让所有的复杂性远离”这样的想法是很棒的。但是你仍然需要在本地生成配置。而且可怕的是需要为这样“简单”的东西使用很多 CLI(LCTT 译注:命令行界面)命令。
|
||||
|
||||
我只是略读了[入门部分][13]的七页,给我的感觉像是_我才是_那个小白。此前我甚至从来没有学习过所谓简单的 “Front Matter” 的语法或者所谓简单的 “Liquid 模板引擎” 的来龙去脉。
|
||||
|
||||
我宁愿去手工编写一个网站。
|
||||
|
||||
老实说我有点惊讶 Facebook 使用它来写 React 文档,因为他们能够用 React 来构建他们的帮助文档,并且在一天之内[预渲染到静态的 HTML 文件][14]。
|
||||
|
||||
他们所需要做的就是利用已有的 Markdown 文件,就像跟使用 CMS 一样。
|
||||
|
||||
我想是这样……
|
||||
|
||||
### #12 使用 GitHub 作为 CMS
|
||||
|
||||
比如说你有一个带有一些文本的网站,但是你并不想在 HTML 的标记中储存那些文本。
|
||||
|
||||
取而代之,你想要把这堆文本存放到某个地方,以便非开发者也可以很容易地编辑。也许要使用某种形式的版本控制。甚至还可能需要一个审查过程。
|
||||
|
||||
这里是我的建议:在你的版本库中使用 markdown 文件存储文本。然后在你的前端使用插件来获取这些文本块并在页面呈现。
|
||||
|
||||
我是 React 的支持者,因此这里有一个 `<Markdown>` 插件的示例,给出一些 markdown 的路径,它就会被获取、解析,并以 HTML 的形式呈现。
|
||||
|
||||
(我正在使用 [marked][1] npm 包来将 markdown 解析为 HTML。)
|
||||
|
||||
这里是我的示例仓库 [/text-snippets][2],里边有一些 markdown 文件 。
|
||||
|
||||
(你也可以使用 GitHub API 来[获取内容][15] —— 但我不确定你是否能搞定。)
|
||||
|
||||
你可以像这样使用插件:
|
||||
|
||||
如此,GitHub 就是你的 CMS 了,可以说,不管有多少文本块都可以放进去。
|
||||
|
||||
上边的示例只是在浏览器上安装好插件后获取 markdown 。如果你想要一个静态站点那么你需要服务器端渲染。
|
||||
|
||||
有个好消息!没有什么能阻止你从服务器中获取所有的 markdown 文件 (并配上各种为你服务的缓存策略)。如果你沿着这条路继续走下去的话,你可能会想要去试试使用 GitHub API 去获取目录中的所有 markdown 文件的列表。
|
||||
|
||||
### 奖励环节 —— GitHub 工具!
|
||||
|
||||
我曾经使用过一段时间的 [Chrome 的扩展 Octotree][16],而且现在我推荐它。虽然不是吐血推荐,但不管怎样我还是推荐它。
|
||||
|
||||
它会在左侧提供一个带有树状视图的面板以显示当前你所查看的仓库。
|
||||
|
||||

|
||||
|
||||
从[这个视频][17]中我了解到了 [octobox][18] ,到目前为止看起来还不错。它是一个 GitHub 工单的收件箱。这一句介绍就够了。
|
||||
|
||||
说到颜色,在上面所有的截图中我都使用了亮色主题,所以希望不要闪瞎你的双眼。不过说真的,我看到的其他东西都是黑色的主题,为什么我非要忍受 GitHub 这个苍白的主题呐?
|
||||
|
||||

|
||||
|
||||
这是由 Chrome 扩展 [Stylish][19](它可以在任何网站使用主题)和 [GitHub Dark][20] 风格的一个组合。要完全黑化,那黑色主题的 Chrome 开发者工具(这是内建的,在设置中打开) 以及 [Atom One Dark for Chrome 主题][21]你肯定也需要。
|
||||
|
||||
### Bitbucket
|
||||
|
||||
这些内容不适合放在这篇文章的任何地方,但是如果我不称赞 Bitbucket 的话,那就不对了。
|
||||
|
||||
两年前我开始了一个项目并花了大半天时间评估哪一个 git 托管服务更适合,最终 Bitbucket 赢得了相当不错的成绩。他们的代码审查流程遥遥领先(这甚至比 GitHub 拥有的指派审阅者的概念要早很长时间)。
|
||||
|
||||
GitHub 后来在这次审查竞赛中追了上来,干的不错。不幸的是在过去的一年里我没有机会再使用 Bitbucket —— 也许他们依然在某些方面领先。所以,我会力劝每一个选择 git 托管服务的人考虑一下 Bitbucket 。
|
||||
|
||||
### 结尾
|
||||
|
||||
就是这样!我希望这里至少有三件事是你此前并不知道的,祝好。
|
||||
|
||||
修订:在评论中有更多的技巧;请尽管留下你自己喜欢的技巧。真的,真心祝好。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://hackernoon.com/12-cool-things-you-can-do-with-github-f3e0424cf2f0
|
||||
|
||||
作者:[David Gilbertson][a]
|
||||
译者:[softpaopao](https://github.com/softpaopao)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://hackernoon.com/@david.gilbertson
|
||||
[1]:https://www.npmjs.com/package/marked
|
||||
[2]:https://github.com/davidgilbertson/about-github/tree/master/text-snippets
|
||||
[3]:https://guides.github.com/features/mastering-markdown/
|
||||
[4]:https://github.com/github/linguist/blob/fc1404985abb95d5bc33a0eba518724f1c3c252e/vendor/README.md
|
||||
[5]:https://help.github.com/articles/closing-issues-using-keywords/
|
||||
[6]:https://help.github.com/articles/searching-issues-and-pull-requests/
|
||||
[7]:https://www.zenhub.com/
|
||||
[8]:https://help.github.com/articles/tracking-the-progress-of-your-work-with-project-boards/
|
||||
[9]:https://github.com/davidgilbertson/about-github/wiki
|
||||
[10]:http://redux.js.org/
|
||||
[11]:https://github.com/davidgilbertson/about-github
|
||||
[12]:https://davidgilbertson.github.io/about-github/
|
||||
[13]:https://jekyllrb.com/docs/home/
|
||||
[14]:https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#pre-rendering-into-static-html-files
|
||||
[15]:https://developer.github.com/v3/repos/contents/#get-contents
|
||||
[16]:https://chrome.google.com/webstore/detail/octotree/bkhaagjahfmjljalopjnoealnfndnagc?hl=en-US
|
||||
[17]:https://www.youtube.com/watch?v=NhlzMcSyQek&index=2&list=PLNYkxOF6rcIB3ci6nwNyLYNU6RDOU3YyL
|
||||
[18]:https://octobox.io/
|
||||
[19]:https://chrome.google.com/webstore/detail/stylish-custom-themes-for/fjnbnpbmkenffdnngjfgmeleoegfcffe/related?hl=en
|
||||
[20]:https://userstyles.org/styles/37035/github-dark
|
||||
[21]:https://chrome.google.com/webstore/detail/atom-one-dark-theme/obfjhhknlilnfgfakanjeimidgocmkim?hl=en
|
||||
@@ -0,0 +1,57 @@
|
||||
如何让网站不下线而从 Redis 2 迁移到 Redis 3
|
||||
============================================================
|
||||
|
||||
我们在 Sky Betting&Gaming 中使用 [Redis][2] 作为共享内存缓存,用于那些需要跨 API 服务器或者 Web 服务器鉴别令牌之类的操作。在 Core Tribe 内,它用来帮助处理日益庞大的登录数量,特别是在繁忙的时候,我们在一分钟内登录数量会超过 20,000 人。这在很大程度上适用于数据存放在大量服务器的情况下(在 SSO 令牌用于 70 台 Apache HTTPD 服务器的情况下)。我们最近着手升级 Redis 服务器,此升级旨在使用 Redis 3.2 提供的原生集群功能。这篇博客希望解释为什么我们要使用集群、我们遇到的问题以及我们的解决方案。
|
||||
|
||||
### 在开始阶段(或至少在升级之前)
|
||||
|
||||
我们的传统缓存中每个缓存都包括一对 Redis 服务器,使用 keepalive 确保始终有一个主节点监听<ruby>浮动 IP <rt>floating IP</rt></ruby>地址。当出现问题时,这些服务器对需要很大的精力来进行管理,而故障模式有时是非常各种各样的。有时,只允许读取它所持有的数据,而不允许写入的从属节点却会得到浮动 IP 地址,这种问题是相对容易诊断的,但会让无论哪个程序试图使用该缓存时都很麻烦。
|
||||
|
||||
### 新的应用程序
|
||||
|
||||
因此,这种情况下,我们需要构建一个新的应用程序,一个使用<ruby>共享内存缓存<rt>shared in-memory cache</rt></ruby>的应用程序,但是我们不希望对该缓存进行迂回的故障切换过程。因此,我们的要求是共享的内存缓存,没有单点故障,可以使用尽可能少的人为干预来应对多种不同的故障模式,并且在事件恢复之后也能够在很少的人为干预下恢复,一个额外的要求是提高缓存的安全性,以减少数据泄露的范围(稍后再说)。当时 Redis Sentinel 看起来很有希望,并且有许多程序支持代理 Redis 连接,比如 [twemproxy][3]。这会导致还要安装其它很多组件,它应该有效,并且人际交互最少,但它复杂而需要运行大量的服务器和服务,并且相互通信。
|
||||
|
||||

|
||||
|
||||
将会有大量的应用服务器与 twemproxy 进行通信,这会将它们的调用路由到合适的 Redis 主节点,twemproxy 将从 sentinal 集群获取主节点的信息,它将控制哪台 Redis 实例是主,哪台是从。这个设置是复杂的,而且仍有单点故障,它依赖于 twemproxy 来处理分片,来连接到正确的 Redis 实例。它具有对应用程序透明的优点,所以我们可以在理论上做到将现有的应用程序转移到这个 Redis 配置,而不用改变应用程序。但是我们要从头开始构建一个应用程序,所以迁移应用程序不是一个必需条件。
|
||||
|
||||
幸运的是,这个时候,Redis 3.2 出来了,而且内置了原生集群,消除了对单一 sentinel 集群需要。
|
||||
|
||||

|
||||
|
||||
它有一个更简单的设置,但 twemproxy 不支持 Redis 集群分片,它能为你分片数据,但是如果尝试在与分片不一致的集群中这样做会导致问题。有参考的指南可以使其匹配,但是集群可以自动改变形式,并改变分片的设置方式。它仍然有单点故障。正是在这一点上,我将永远感谢我的一位同事发现了一个 Node.js 的 Redis 的集群发现驱动程序,让我们完全放弃了 twemproxy。
|
||||
|
||||

|
||||
|
||||
因此,我们能够自动分片数据,故障转移和故障恢复基本上是自动的。应用程序知道哪些节点存在,并且在写入数据时,如果写入错误的节点,集群将自动重定向该写入。这是被选的配置,这让我们共享的内存缓存相当健壮,可以没有干预地应付基本的故障模式。在测试期间,我们的确发现了一些缺陷。复制是在一个接一个节点的基础上进行的,因此如果我们丢失了一个主节点,那么它的从节点会成为一个单点故障,直到死去的节点恢复服务,也只有主节点对集群健康投票,所以如果我们一下失去太多主节点,那么集群无法自我恢复。但这比我们过去的好。
|
||||
|
||||
### 向前进
|
||||
|
||||
随着使用集群 Redis 配置的新程序,我们对于老式 Redis 实例的状态变得越来越不适应,但是新程序与现有程序的规模并不相同(超过 30GB 的内存专用于我们最大的老式 Redis 实例数据库)。因此,随着 Redis 集群在底层得到了证实,我们决定迁移老式的 Redis 实例到新的 Redis 集群中。
|
||||
|
||||
由于我们有一个原生支持 Redis 集群的 Node.js Redis 驱动程序,因此我们开始将 Node.js 程序迁移到 Redis 集群。但是,如何将数十亿字节的数据从一个地方移动到另一个地方,而不会造成重大问题?特别是考虑到这些数据是认证令牌,所以如果它们错了,我们的终端用户将会被登出。一个选择是要求网站完全下线,将所有内容都指向新的 Redis 群集,并将数据迁移到其中,以希望获得最佳效果。另一个选择是切换到新集群,并强制所有用户再次登录。由于显而易见的原因,这些都不是非常合适的。我们决定采取的替代方法是将数据同时写入老式 Redis 实例和正在替换它的集群,同时随着时间的推移,我们将逐渐更多地向该集群读取。由于数据的有效期有限(令牌在几个小时后到期),这种方法可以导致零停机,并且不会有数据丢失的风险。所以我们这么做了。迁移是成功的。
|
||||
|
||||
剩下的就是服务于我们的 PHP 代码(其中还有一个项目是有用的,其它的最终是没必要的)的 Redis 的实例了,我们在这过程中遇到了一个困难,实际上是两个。首先,也是最紧迫的是找到在 PHP 中使用的 Redis 集群发现驱动程序,还要是我们正在使用的 PHP 版本。这被证明是可行的,因为我们升级到了最新版本的 PHP。我们选择的驱动程序不喜欢使用 Redis 的授权方式,因此我们决定使用 Redis 集群作为一个额外的安全步骤 (我告诉你,这将有更多的安全性)。当我们用 Redis 集群替换每个老式 Redis 实例时,修复似乎很直接,将 Redis 授权关闭,这样它将会响应所有的请求。然而,这并不是真的,由于某些原因,Redis 集群不会接受来自 Web 服务器的连接。 Redis 在版本 3 中引入的称为“保护模式”的新安全功能将在 Redis 绑定到任何接口时将停止监听来自外部 IP 地址的连接,并无需配置 Redis 授权密码。这被证明相当容易修复,但让我们保持警惕。
|
||||
|
||||
### 现在?
|
||||
|
||||
这就是我们现在的情况。我们已经迁移了我们的一些老式 Redis 实例,并且正在迁移其余的。我们通过这样做解决了我们的一些技术债务,并提高了我们的平台的稳定性。使用 Redis 集群,我们还可以扩展内存数据库并扩展它们。 Redis 是单线程的,所以只要在单个实例中留出更多的内存就会可以得到这么多的增长,而且我们已经紧跟在这个限制后面。我们期待着从新的集群中获得改进的性能,同时也为我们提供了扩展和负载均衡的更多选择。
|
||||
|
||||
### 未来怎么样?
|
||||
|
||||
我们解决了一些技术性债务,这使我们的服务更容易支持,更加稳定。但这并不意味着这项工作完成了,Redis 4 似乎有一些我们可能想要研究的功能。而且 Redis 并不是我们使用的唯一软件。我们将继续努力改进平台,缩短处理技术债务的时间,但随着客户群体的扩大,我们力求提供更丰富的服务,我们总是会遇到需要改进的事情。下一个挑战可能与每分钟超过 20,000次 登录到超过 40,000 次甚至更高的扩展有关。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: http://engineering.skybettingandgaming.com/2017/09/25/redis-2-to-redis-3/
|
||||
|
||||
作者:[Craig Stewart][a]
|
||||
译者:[ ](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:http://engineering.skybettingandgaming.com/authors#craig_stewart
|
||||
[1]:http://engineering.skybettingandgaming.com/category/devops/
|
||||
[2]:https://redis.io/
|
||||
[3]:https://github.com/twitter/twemproxy
|
||||
@@ -0,0 +1,204 @@
|
||||
每个安卓开发初学者应该了解的 12 个技巧
|
||||
====
|
||||
|
||||
> 一次掌握一个技巧,更好地学习安卓
|
||||
|
||||

|
||||
|
||||
距离安迪·鲁宾和他的团队着手开发一个希望颠覆传统手机操作模式的操作系统已经过去 12 年了,这套系统有可能让手机或者智能机给消费者以及软件开发人员带来全新的体验。之前的智能机仅限于收发短信和查看电子邮件(当然还可以打电话),给用户和开发者带来很大的限制。
|
||||
|
||||
安卓,作为打破这个枷锁的系统,拥有非常优秀的框架设计,给大家提供的不仅仅是一组有限的功能,更多的是自由的探索。有人会说 iPhone 才是手机产业的颠覆产品,不过我们说的不是 iPhone 有多么酷(或者多么贵,是吧?),它还是有限制的,而这是我们从来都不希望有的。
|
||||
|
||||
不过,就像本大叔说的,能力越大责任越大,我们也需要更加认真对待安卓应用的设计方式。我看到很多教程都忽略了向初学者传递这个理念,在动手之前请先充分理解系统架构。他们只是把一堆的概念和代码丢给读者,却没有解释清楚相关的优缺点,它们对系统的影响,以及该用什么不该用什么等等。
|
||||
|
||||
在这篇文章里,我们将介绍一些初学者以及中级开发人员都应该掌握的技巧,以帮助更好地理解安卓框架。后续我们还会在这个系列里写更多这样的关于实用技巧的文章。我们开始吧。
|
||||
|
||||
### 1、 `@+id` 和 `@id` 的区别
|
||||
|
||||
要在 Java 代码里访问一个图形控件(或组件),或者是要让它成为其他控件的依赖,我们需要一个唯一的值来引用它。这个唯一值用 `android:id` 属性来定义,本质上就是把用户提供的 id 附加到 `@+id/` 后面,写入到 _id 资源文件_,供其他控件使用。一个 Toolbar 的 id 可以这样定义,
|
||||
|
||||
```
|
||||
android:id="@+id/toolbar"
|
||||
```
|
||||
|
||||
然后这个 id 值就能被 `findViewById(…)` 识别,这个函数会在资源文件里查找 id,或者直接从 R.id 路径引用,然后返回所查找的 View 的类型。
|
||||
|
||||
而另一种,`@id`,和 `findViewById(…)` 行为一样 - 也会根据提供的 id 查找组件,不过仅限于布局时使用。一般用来布置相关控件。
|
||||
|
||||
```
|
||||
android:layout_below="@id/toolbar"
|
||||
```
|
||||
|
||||
### 2、 使用 `@string` 资源为 XML 提供字符串
|
||||
|
||||
简单来说,就是不要在 XML 里直接用字符串。原因很简单。当我们在 XML 里直接使用了字符串,我们一般会在其它地方再次用到同样的字符串。想像一下当我们需要在不同的地方调整同一个字符串的噩梦,而如果使用字符串资源就只改一个地方就够了。另一个好处是,使用资源文件可以提供多国语言支持,因为可以为不同的语言创建相应的字符串资源文件。
|
||||
|
||||
```
|
||||
android:text="My Awesome Application"
|
||||
```
|
||||
|
||||
当你直接使用字符串时,你会在 Android Studio 里收到警告,提示说应该把写死的字符串改成字符串资源。可以点击这个提示,然后按下 `ALT + ENTER` 打开字符串编辑。你也可以直接打开 `res` 目录下的 `values` 目录里的 `strings.xml` 文件,然后像下面这样声明一个字符串资源。
|
||||
|
||||
```
|
||||
<string name="app_name">My Awesome Application</string>
|
||||
```
|
||||
|
||||
然后用它来替换写死的字符串,
|
||||
|
||||
```
|
||||
android:text="@string/app_name"
|
||||
```
|
||||
|
||||
### 3、 使用 `@android` 和 `?attr` 常量
|
||||
|
||||
尽量使用系统预先定义的常量而不是重新声明。举个例子,在布局中有几个地方要用白色或者 #ffffff 颜色值。不要每次都直接用 #ffffff 数值,也不要自己为白色重新声明资源,我们可以直接用这个,
|
||||
|
||||
```
|
||||
@android:color/white
|
||||
```
|
||||
|
||||
安卓预先定义了很多常用的颜色常量,比如白色,黑色或粉色。最经典的应用场景是透明色:
|
||||
|
||||
```
|
||||
@android:color/transparent
|
||||
```
|
||||
|
||||
另一个引用常量的方式是 `?attr`,用来将预先定义的属性值赋值给不同的属性。举个自定义 Toolbar 的例子。这个 Toolbar 需要定义宽度和高度。宽度通常可以设置为 `MATCH_PARENT`,但高度呢?我们大多数人都没有注意设计指导,只是简单地随便设置一个看上去差不多的值。这样做不对。不应该随便自定义高度,而应该这样做,
|
||||
|
||||
```
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
```
|
||||
|
||||
`?attr` 的另一个应用是点击视图时画水波纹效果。`SelectableItemBackground` 是一个预定义的 drawable,任何视图需要增加波纹效果时可以将它设为背景:
|
||||
|
||||
```
|
||||
android:background="?attr/selectableItemBackground"
|
||||
```
|
||||
|
||||
也可以用这个:
|
||||
|
||||
```
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
```
|
||||
|
||||
来显示无边框波纹。
|
||||
|
||||
### 4、 SP 和 DP 的区别
|
||||
|
||||
虽然这两个没有本质上的区别,但知道它们是什么以及在什么地方适合用哪个很重要。
|
||||
|
||||
SP 的意思是缩放无关像素,一般建议用于 TextView,首先文字不会因为显示密度不同而显示效果不一样,另外 TextView 的内容还需要根据用户设定做拉伸,或者只调整字体大小。
|
||||
|
||||
其他需要定义尺寸和位置的地方,可以使用 DP,也就是密度无关像素。之前说过,DP 和 SP 的性质是一样的,只是 DP 会根据显示密度自动拉伸,因为安卓系统会动态计算实际显示的像素,这样就可以让使用 DP 的组件在不同显示密度的设备上都可以拥有相同的显示效果。
|
||||
|
||||
### 5、 Drawable 和 Mipmap 的应用
|
||||
|
||||
这两个最让人困惑的是 - drawable 和 mipmap 有多少差异?
|
||||
|
||||
虽然这两个好像有同样的用途,但它们设计目的不一样。mipmap 是用来储存图标的,而 drawable 用于任何其他格式。我们可以看一下系统内部是如何使用它们的,就知道为什么不能混用了。
|
||||
|
||||
你可以看到你的应用里有几个 mipmap 和 drawable 目录,每一个分别代表不同的显示分辨率。当系统从 drawable 目录读取资源时,只会根据当前设备的显示密度选择确定的目录。然而,在读取 mipmap 时,系统会根据需要选择合适的目录,而不仅限于当前显示密度,主要是因为有些启动器会故意显示较大的图标,所以系统会使用较大分辨率的资源。
|
||||
|
||||
总之,用 mipmap 来存放图标或标记图片,可以在不同显示密度的设备上看到分辨率变化,而其它根据需要显示的图片资源都用 drawable。
|
||||
|
||||
比如说,Nexus 5 的显示分辨率是 xxhdpi。当我们把图标放到 `mipmap` 目录里时,所有 `mipmap` 目录都将读入内存。而如果放到 drawable 里,只有 `drawable-xxhdpi` 目录会被读取,其他目录都会被忽略。
|
||||
|
||||
### 6、 使用矢量图形
|
||||
|
||||
为了支持不同显示密度的屏幕,将同一个资源的多个版本(大小)添加到项目里是一个很常见的技巧。这种方式确实有用,不过它也会带来一定的性能开支,比如更大的 apk 文件以及额外的开发工作。为了消除这种影响,谷歌的安卓团队发布了新增的矢量图形。
|
||||
|
||||
矢量图形是用 XML 描述的 SVG(可拉伸矢量图形),是用点、直线和曲线组合以及填充颜色绘制出的图形。正因为矢量图形是由点和线动态画出来的,在不同显示密度下拉伸也不会损失分辨率。而矢量图形带来的另一个好处是更容易做动画。往一个 AnimatedVectorDrawable 文件里添加多个矢量图形就可以做出动画,而不用添加多张图片然后再分别处理。
|
||||
|
||||
```
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
|
||||
<path android:fillColor="#69cdff" android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z"/>
|
||||
|
||||
</vector>
|
||||
```
|
||||
|
||||
上面的向量定义可以画出下面的图形,
|
||||
|
||||

|
||||
|
||||
要在你的安卓项目里添加矢量图形,可以右键点击你项目里的应用模块,然后选择 New >> Vector Assets。然后会打开 Assets Studio,你可以有两种方式添加矢量图形。第一种是从 Material 图标里选择,另一种是选择本地的 SVG 或 PSD 文件。
|
||||
|
||||
谷歌建议与应用相关都使用 Material 图标,来保持安卓的连贯性和统一体验。[这里][1]有全部图标,记得看一下。
|
||||
|
||||
### 7、 设定边界的开始和结束
|
||||
|
||||
这是人们最容易忽略的地方之一。边界!增加边界当然很简单,但是如果要考虑支持很旧的平台呢?
|
||||
|
||||
边界的“开始”和“结束”分别是“左”和“右”的超集,所以如果应用的 `minSdkVersion` 是 17 或更低,边界和填充的“开始”和“结束”定义是旧的“左”/“右”所需要的。在那些没有定义“开始”和“结束”的系统上,这两个定义可以被安全地忽略。可以像下面这样声明:
|
||||
|
||||
```
|
||||
android:layout_marginEnd="20dp"
|
||||
android:paddingStart="20dp"
|
||||
```
|
||||
|
||||
### 8、 使用 Getter/Setter 生成工具
|
||||
|
||||
在创建一个容器类(只是用来简单的存放一些变量数据)时很烦的一件事情是写多个 getter 和 setter,复制/粘贴该方法的主体再为每个变量重命名。
|
||||
|
||||
幸运的是,Android Studio 有一个解决方法。可以这样做,在类里声明你需要的所有变量,然后打开 Toolbar >> Code。快捷方式是 `ALT + Insert`。点击 Code 会显示 Generate,点击它会出来很多选项,里面有 Getter 和 Setter 选项。在保持焦点在你的类页面然后点击,就会为当前类添加所有的 getter 和 setter(有需要的话可以再去之前的窗口操作)。很爽吧。
|
||||
|
||||
### 9、 使用 Override/Implement 生成工具
|
||||
|
||||
这是另一个很好用的生成工具。自定义一个类然后再扩展很容易,但是如果要扩展你不熟悉的类呢。比如说 PagerAdapter,你希望用 ViewPager 来展示一些页面,那就需要定制一个 PagerAdapter 并实现它的重载方法。但是具体有哪些方法呢?Android Studio 非常贴心地为自定义类强行添加了一个构造函数,或者可以用快捷键(`ALT + Enter`),但是父类 PagerAdapter 里的其他(虚拟)方法需要自己手动添加,我估计大多数人都觉得烦。
|
||||
|
||||
要列出所有可以重载的方法,可以点击 Code >> Generate and Override methods 或者 Implement methods,根据你的需要。你还可以为你的类选择多个方法,只要按住 Ctrl 再选择方法,然后点击 OK。
|
||||
|
||||
### 10、 正确理解 Context
|
||||
|
||||
Context 有点恐怖,我估计许多初学者从没有认真理解过 Context 类的结构 - 它是什么,为什么到处都要用到它。
|
||||
|
||||
简单地说,它将你能从屏幕上看到的所有内容都整合在一起。所有的视图(或者它们的扩展)都通过 Context 绑定到当前的环境。Context 用来管理应用层次的资源,比如说显示密度,或者当前的关联活动。活动、服务和应用都实现了 Context 类的接口来为其他关联组件提供内部资源。举个添加到 MainActivity 的 TextView 的例子。你应该注意到了,在创建一个对象的时候,TextView 的构造函数需要 Context 参数。这是为了获取 TextView 里定义到的资源。比如说,TextView 需要在内部用到 Roboto 字体。这样的话,TextView 需要 Context。而且在我们将 Context(或者 `this`)传递给 TextView 的时候,也就是告诉它绑定当前活动的生命周期。
|
||||
|
||||
另一个 Context 的关键应用是初始化应用层次的操作,比如初始化一个库。库的生命周期和应用是不相关的,所以它需要用 `getApplicationContext()` 来初始化,而不是用 `getContext` 或 `this` 或 `getActivity()`。掌握正确使用不同 Context 类型非常重要,可以避免内存泄漏。另外,要用到 Context 来启动一个活动或服务。还记得 `startActivity(…)` 吗?当你需要在一个非活动类里切换活动时,你需要一个 Context 对象来调用 `startActivity` 方法,因为它是 Context 类的方法,而不是 Activity 类。
|
||||
|
||||
```
|
||||
getContext().startActivity(getContext(), SecondActivity.class);
|
||||
```
|
||||
|
||||
如果你想了解更多 Context 的行为,可以看看[这里][2]或[这里][3]。第一个是一篇关于 Context 的很好的文章,介绍了在哪些地方要用到它。而另一个是安卓关于 Context 的文档,全面介绍了所有的功能 - 方法,静态标识以及更多。
|
||||
|
||||
### 奖励 #1: 格式化代码
|
||||
|
||||
有人会不喜欢整齐,统一格式的代码吗?好吧,几乎我们每一个人,在写一个超过 1000 行的类的时候,都希望我们的代码能有合适的结构。而且,并不仅仅大的类才需要格式化,每一个小模块类也需要让代码保持可读性。
|
||||
|
||||
使用 Android Studio,或者任何 JetBrains IDE,你都不需要自己手动整理你的代码,像增加缩进或者 = 之前的空格。就按自己希望的方式写代码,在想要格式化的时候,如果是 Windows 系统可以按下 `ALT + CTRL + L`,Linux 系统按下 `ALT + CTRL + SHIFT + L`。*代码就自动格式化好了*
|
||||
|
||||
### 奖励 #2: 使用库
|
||||
|
||||
面向对象编程的一个重要原则是增加代码的可重用性,或者说减少重新发明轮子的习惯。很多初学者错误地遵循了这个原则。这条路有两个方向,
|
||||
|
||||
- 不用任何库,自己写所有的代码。
|
||||
- 用库来处理所有事情。
|
||||
|
||||
不管哪个方向走到底都是不对的。如果你彻底选择第一个方向,你将消耗大量的资源,仅仅是为了满足自己拥有一切的骄傲。很可能你的代码没有做过替代库那么多的测试,从而增加模块出问题的可能。如果资源有限,不要重复发明轮子。直接用经过测试的库,在有了明确目标以及充分的资源后,可以用自己的可靠代码来替换这个库。
|
||||
|
||||
而彻底走向另一个方向,问题更严重 - 别人代码的可靠性。不要习惯于所有事情都依赖于别人的代码。在不用太多资源或者自己能掌控的情况下尽量自己写代码。你不需要用库来自定义一个 TypeFaces(字体),你可以自己写一个。
|
||||
|
||||
所以要记住,在这两个极端中间平衡一下 - 不要重新创造所有事情,也不要过分依赖外部代码。保持中立,根据自己的能力写代码。
|
||||
|
||||
这篇文章最早发布在 [What’s That Lambda][4] 上。请访问网站阅读更多关于 Android、Node.js、Angular.js 等等类似文章。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://android.jlelse.eu/12-practices-every-android-beginner-should-know-cd43c3710027
|
||||
|
||||
作者:[Nilesh Singh][a]
|
||||
译者:[zpl1025](https://github.com/zpl1025)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://android.jlelse.eu/@nileshsingh?source=post_header_lockup
|
||||
[1]:https://material.io/icons/
|
||||
[2]:https://blog.mindorks.com/understanding-context-in-android-application-330913e32514
|
||||
[3]:https://developer.android.com/reference/android/content/Context.html
|
||||
[4]:https://www.whatsthatlambda.com/android/android-dev-101-things-every-beginner-must-know
|
||||
@@ -0,0 +1,206 @@
|
||||
并发服务器(一):简介
|
||||
============================================================
|
||||
|
||||
这是关于并发网络服务器编程的第一篇教程。我计划测试几个主流的、可以同时处理多个客户端请求的服务器并发模型,基于可扩展性和易实现性对这些模型进行评判。所有的服务器都会监听套接字连接,并且实现一些简单的协议用于与客户端进行通讯。
|
||||
|
||||
该系列的所有文章:
|
||||
|
||||
* [第一节 - 简介][7]
|
||||
* [第二节 - 线程][8]
|
||||
* [第三节 - 事件驱动][9]
|
||||
|
||||
### 协议
|
||||
|
||||
该系列教程所用的协议都非常简单,但足以展示并发服务器设计的许多有趣层面。而且这个协议是 _有状态的_ —— 服务器根据客户端发送的数据改变内部状态,然后根据内部状态产生相应的行为。并非所有的协议都是有状态的 —— 实际上,基于 HTTP 的许多协议是无状态的,但是有状态的协议也是很常见,值得认真讨论。
|
||||
|
||||
在服务器端看来,这个协议的视图是这样的:
|
||||
|
||||

|
||||
|
||||
总之:服务器等待新客户端的连接;当一个客户端连接的时候,服务器会向该客户端发送一个 `*` 字符,进入“等待消息”的状态。在该状态下,服务器会忽略客户端发送的所有字符,除非它看到了一个 `^` 字符,这表示一个新消息的开始。这个时候服务器就会转变为“正在通信”的状态,这时它会向客户端回送数据,把收到的所有字符的每个字节加 1 回送给客户端^注1 。当客户端发送了 `$` 字符,服务器就会退回到等待新消息的状态。`^` 和 `$` 字符仅仅用于分隔消息 —— 它们不会被服务器回送。
|
||||
|
||||
每个状态之后都有个隐藏的箭头指向 “等待客户端” 状态,用来客户端断开连接。因此,客户端要表示“我已经结束”的方法很简单,关掉它那一端的连接就好。
|
||||
|
||||
显然,这个协议是真实协议的简化版,真实使用的协议一般包含复杂的报文头、转义字符序列(例如让消息体中可以出现 `$` 符号),额外的状态变化。但是我们这个协议足以完成期望。
|
||||
|
||||
另一点:这个系列是介绍性的,并假设客户端都工作的很好(虽然可能运行很慢);因此没有设置超时,也没有设置特殊的规则来确保服务器不会因为客户端的恶意行为(或是故障)而出现阻塞,导致不能正常结束。
|
||||
|
||||
### 顺序服务器
|
||||
|
||||
这个系列中我们的第一个服务端程序是一个简单的“顺序”服务器,用 C 进行编写,除了标准的 POSIX 中用于套接字的内容以外没有使用其它库。服务器程序是顺序,因为它一次只能处理一个客户端的请求;当有客户端连接时,像之前所说的那样,服务器会进入到状态机中,并且不再监听套接字接受新的客户端连接,直到当前的客户端结束连接。显然这不是并发的,而且即便在很少的负载下也不能服务多个客户端,但它对于我们的讨论很有用,因为我们需要的是一个易于理解的基础。
|
||||
|
||||
这个服务器的完整代码在[这里][11];接下来,我会着重于一些重点的部分。`main` 函数里面的外层循环用于监听套接字,以便接受新客户端的连接。一旦有客户端进行连接,就会调用 `serve_connection`,这个函数中的代码会一直运行,直到客户端断开连接。
|
||||
|
||||
顺序服务器在循环里调用 `accept` 用来监听套接字,并接受新连接:
|
||||
|
||||
```
|
||||
while (1) {
|
||||
struct sockaddr_in peer_addr;
|
||||
socklen_t peer_addr_len = sizeof(peer_addr);
|
||||
|
||||
int newsockfd =
|
||||
accept(sockfd, (struct sockaddr*)&peer_addr, &peer_addr_len);
|
||||
|
||||
if (newsockfd < 0) {
|
||||
perror_die("ERROR on accept");
|
||||
}
|
||||
|
||||
report_peer_connected(&peer_addr, peer_addr_len);
|
||||
serve_connection(newsockfd);
|
||||
printf("peer done\n");
|
||||
}
|
||||
```
|
||||
|
||||
`accept` 函数每次都会返回一个新的已连接的套接字,然后服务器调用 `serve_connection`;注意这是一个 _阻塞式_ 的调用 —— 在 `serve_connection` 返回前,`accept` 函数都不会再被调用了;服务器会被阻塞,直到客户端结束连接才能接受新的连接。换句话说,客户端按 _顺序_ 得到响应。
|
||||
|
||||
这是 `serve_connection` 函数:
|
||||
|
||||
```
|
||||
typedef enum { WAIT_FOR_MSG, IN_MSG } ProcessingState;
|
||||
|
||||
void serve_connection(int sockfd) {
|
||||
if (send(sockfd, "*", 1, 0) < 1) {
|
||||
perror_die("send");
|
||||
}
|
||||
|
||||
ProcessingState state = WAIT_FOR_MSG;
|
||||
|
||||
while (1) {
|
||||
uint8_t buf[1024];
|
||||
int len = recv(sockfd, buf, sizeof buf, 0);
|
||||
if (len < 0) {
|
||||
perror_die("recv");
|
||||
} else if (len == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
switch (state) {
|
||||
case WAIT_FOR_MSG:
|
||||
if (buf[i] == '^') {
|
||||
state = IN_MSG;
|
||||
}
|
||||
break;
|
||||
case IN_MSG:
|
||||
if (buf[i] == '$') {
|
||||
state = WAIT_FOR_MSG;
|
||||
} else {
|
||||
buf[i] += 1;
|
||||
if (send(sockfd, &buf[i], 1, 0) < 1) {
|
||||
perror("send error");
|
||||
close(sockfd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(sockfd);
|
||||
}
|
||||
```
|
||||
|
||||
它完全是按照状态机协议进行编写的。每次循环的时候,服务器尝试接收客户端的数据。收到 0 字节意味着客户端断开连接,然后循环就会退出。否则,会逐字节检查接收缓存,每一个字节都可能会触发一个状态。
|
||||
|
||||
`recv` 函数返回接收到的字节数与客户端发送消息的数量完全无关(`^...$` 闭合序列的字节)。因此,在保持状态的循环中遍历整个缓冲区很重要。而且,每一个接收到的缓冲中可能包含多条信息,但也有可能开始了一个新消息,却没有显式的结束字符;而这个结束字符可能在下一个缓冲中才能收到,这就是处理状态在循环迭代中进行维护的原因。
|
||||
|
||||
例如,试想主循环中的 `recv` 函数在某次连接中返回了三个非空的缓冲:
|
||||
|
||||
1. `^abc$de^abte$f`
|
||||
2. `xyz^123`
|
||||
3. `25$^ab$abab`
|
||||
|
||||
服务端返回的是哪些数据?追踪代码对于理解状态转变很有用。(答案见^注[2] )
|
||||
|
||||
### 多个并发客户端
|
||||
|
||||
如果多个客户端在同一时刻向顺序服务器发起连接会发生什么事情?
|
||||
|
||||
服务器端的代码(以及它的名字 “顺序服务器”)已经说的很清楚了,一次只能处理 _一个_ 客户端的请求。只要服务器在 `serve_connection` 函数中忙于处理客户端的请求,就不会接受别的客户端的连接。只有当前的客户端断开了连接,`serve_connection` 才会返回,然后最外层的循环才能继续执行接受其他客户端的连接。
|
||||
|
||||
为了演示这个行为,[该系列教程的示例代码][13] 包含了一个 Python 脚本,用于模拟几个想要同时连接服务器的客户端。每一个客户端发送类似之前那样的三个数据缓冲 ^注3 ,不过每次发送数据之间会有一定延迟。
|
||||
|
||||
客户端脚本在不同的线程中并发地模拟客户端行为。这是我们的序列化服务器与客户端交互的信息记录:
|
||||
|
||||
```
|
||||
$ python3.6 simple-client.py -n 3 localhost 9090
|
||||
INFO:2017-09-16 14:14:17,763:conn1 connected...
|
||||
INFO:2017-09-16 14:14:17,763:conn1 sending b'^abc$de^abte$f'
|
||||
INFO:2017-09-16 14:14:17,763:conn1 received b'b'
|
||||
INFO:2017-09-16 14:14:17,802:conn1 received b'cdbcuf'
|
||||
INFO:2017-09-16 14:14:18,764:conn1 sending b'xyz^123'
|
||||
INFO:2017-09-16 14:14:18,764:conn1 received b'234'
|
||||
INFO:2017-09-16 14:14:19,764:conn1 sending b'25$^ab0000$abab'
|
||||
INFO:2017-09-16 14:14:19,765:conn1 received b'36bc1111'
|
||||
INFO:2017-09-16 14:14:19,965:conn1 disconnecting
|
||||
INFO:2017-09-16 14:14:19,966:conn2 connected...
|
||||
INFO:2017-09-16 14:14:19,967:conn2 sending b'^abc$de^abte$f'
|
||||
INFO:2017-09-16 14:14:19,967:conn2 received b'b'
|
||||
INFO:2017-09-16 14:14:20,006:conn2 received b'cdbcuf'
|
||||
INFO:2017-09-16 14:14:20,968:conn2 sending b'xyz^123'
|
||||
INFO:2017-09-16 14:14:20,969:conn2 received b'234'
|
||||
INFO:2017-09-16 14:14:21,970:conn2 sending b'25$^ab0000$abab'
|
||||
INFO:2017-09-16 14:14:21,970:conn2 received b'36bc1111'
|
||||
INFO:2017-09-16 14:14:22,171:conn2 disconnecting
|
||||
INFO:2017-09-16 14:14:22,171:conn0 connected...
|
||||
INFO:2017-09-16 14:14:22,172:conn0 sending b'^abc$de^abte$f'
|
||||
INFO:2017-09-16 14:14:22,172:conn0 received b'b'
|
||||
INFO:2017-09-16 14:14:22,210:conn0 received b'cdbcuf'
|
||||
INFO:2017-09-16 14:14:23,173:conn0 sending b'xyz^123'
|
||||
INFO:2017-09-16 14:14:23,174:conn0 received b'234'
|
||||
INFO:2017-09-16 14:14:24,175:conn0 sending b'25$^ab0000$abab'
|
||||
INFO:2017-09-16 14:14:24,176:conn0 received b'36bc1111'
|
||||
INFO:2017-09-16 14:14:24,376:conn0 disconnecting
|
||||
```
|
||||
|
||||
这里要注意连接名:`conn1` 是第一个连接到服务器的,先跟服务器交互了一段时间。接下来的连接 `conn2` —— 在第一个断开连接后,连接到了服务器,然后第三个连接也是一样。就像日志显示的那样,每一个连接让服务器变得繁忙,持续了大约 2.2 秒的时间(这实际上是人为地在客户端代码中加入的延迟),在这段时间里别的客户端都不能连接。
|
||||
|
||||
显然,这不是一个可扩展的策略。这个例子中,客户端中加入了延迟,让服务器不能处理别的交互动作。一个智能服务器应该能处理一堆客户端的请求,而这个原始的服务器在结束连接之前一直繁忙(我们将会在之后的章节中看到如何实现智能的服务器)。尽管服务端有延迟,但这不会过度占用 CPU;例如,从数据库中查找信息(时间基本上是花在连接到数据库服务器上,或者是花在硬盘中的本地数据库)。
|
||||
|
||||
### 总结及期望
|
||||
|
||||
这个示例服务器达成了两个预期目标:
|
||||
|
||||
1. 首先是介绍了问题范畴和贯彻该系列文章的套接字编程基础。
|
||||
2. 对于并发服务器编程的抛砖引玉 —— 就像之前的部分所说,顺序服务器还不能在非常轻微的负载下进行扩展,而且没有高效的利用资源。
|
||||
|
||||
在看下一篇文章前,确保你已经理解了这里所讲的服务器/客户端协议,还有顺序服务器的代码。我之前介绍过了这个简单的协议;例如 [串行通信分帧][15] 和 [用协程来替代状态机][16]。要学习套接字网络编程的基础,[Beej 的教程][17] 用来入门很不错,但是要深入理解我推荐你还是看本书。
|
||||
|
||||
如果有什么不清楚的,请在评论区下进行评论或者向我发送邮件。深入理解并发服务器!
|
||||
|
||||
***
|
||||
|
||||
- 注1:状态转变中的 In/Out 记号是指 [Mealy machine][2]。
|
||||
- 注2:回应的是 `bcdbcuf23436bc`。
|
||||
- 注3:这里在结尾处有一点小区别,加了字符串 `0000` —— 服务器回应这个序列,告诉客户端让其断开连接;这是一个简单的握手协议,确保客户端有足够的时间接收到服务器发送的所有回复。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction/
|
||||
|
||||
作者:[Eli Bendersky][a]
|
||||
译者:[GitFuture](https://github.com/GitFuture)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://eli.thegreenplace.net/pages/about
|
||||
[1]:https://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction/#id1
|
||||
[2]:https://en.wikipedia.org/wiki/Mealy_machine
|
||||
[3]:https://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction/#id2
|
||||
[4]:https://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction/#id3
|
||||
[5]:https://eli.thegreenplace.net/tag/concurrency
|
||||
[6]:https://eli.thegreenplace.net/tag/c-c
|
||||
[7]:http://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction/
|
||||
[8]:http://eli.thegreenplace.net/2017/concurrent-servers-part-2-threads/
|
||||
[9]:http://eli.thegreenplace.net/2017/concurrent-servers-part-3-event-driven/
|
||||
[10]:https://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction/#id4
|
||||
[11]:https://github.com/eliben/code-for-blog/blob/master/2017/async-socket-server/sequential-server.c
|
||||
[12]:https://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction/#id5
|
||||
[13]:https://github.com/eliben/code-for-blog/tree/master/2017/async-socket-server
|
||||
[14]:https://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction/#id6
|
||||
[15]:http://eli.thegreenplace.net/2009/08/12/framing-in-serial-communications/
|
||||
[16]:http://eli.thegreenplace.net/2009/08/29/co-routines-as-an-alternative-to-state-machines
|
||||
[17]:http://beej.us/guide/bgnet/
|
||||
[18]:https://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction/
|
||||
294
published/201710/20171004 Concurrent Servers Part 2 - Threads.md
Normal file
294
published/201710/20171004 Concurrent Servers Part 2 - Threads.md
Normal file
@@ -0,0 +1,294 @@
|
||||
并发服务器(二):线程
|
||||
============================================================
|
||||
|
||||
这是并发网络服务器系列的第二节。[第一节][20] 提出了服务端实现的协议,还有简单的顺序服务器的代码,是这整个系列的基础。
|
||||
|
||||
这一节里,我们来看看怎么用多线程来实现并发,用 C 实现一个最简单的多线程服务器,和用 Python 实现的线程池。
|
||||
|
||||
该系列的所有文章:
|
||||
|
||||
* [第一节 - 简介][8]
|
||||
* [第二节 - 线程][9]
|
||||
* [第三节 - 事件驱动][10]
|
||||
|
||||
### 多线程的方法设计并发服务器
|
||||
|
||||
说起第一节里的顺序服务器的性能,最显而易见的,是在服务器处理客户端连接时,计算机的很多资源都被浪费掉了。尽管假定客户端快速发送完消息,不做任何等待,仍然需要考虑网络通信的开销;网络要比现在的 CPU 慢上百万倍还不止,因此 CPU 运行服务器时会等待接收套接字的流量,而大量的时间都花在完全不必要的等待中。
|
||||
|
||||
这里是一份示意图,表明顺序时客户端的运行过程:
|
||||
|
||||

|
||||
|
||||
这个图片上有 3 个客户端程序。棱形表示客户端的“到达时间”(即客户端尝试连接服务器的时间)。黑色线条表示“等待时间”(客户端等待服务器真正接受连接所用的时间),有色矩形表示“处理时间”(服务器和客户端使用协议进行交互所用的时间)。有色矩形的末端表示客户端断开连接。
|
||||
|
||||
上图中,绿色和橘色的客户端尽管紧跟在蓝色客户端之后到达服务器,也要等到服务器处理完蓝色客户端的请求。这时绿色客户端得到响应,橘色的还要等待一段时间。
|
||||
|
||||
多线程服务器会开启多个控制线程,让操作系统管理 CPU 的并发(使用多个 CPU 核心)。当客户端连接的时候,创建一个线程与之交互,而在主线程中,服务器能够接受其他的客户端连接。下图是该模式的时间轴:
|
||||
|
||||

|
||||
|
||||
### 每个客户端一个线程,在 C 语言里要用 pthread
|
||||
|
||||
这篇文章的 [第一个示例代码][11] 是一个简单的 “每个客户端一个线程” 的服务器,用 C 语言编写,使用了 [phtreads API][12] 用于实现多线程。这里是主循环代码:
|
||||
|
||||
```
|
||||
while (1) {
|
||||
struct sockaddr_in peer_addr;
|
||||
socklen_t peer_addr_len = sizeof(peer_addr);
|
||||
|
||||
int newsockfd =
|
||||
accept(sockfd, (struct sockaddr*)&peer_addr, &peer_addr_len);
|
||||
|
||||
if (newsockfd < 0) {
|
||||
perror_die("ERROR on accept");
|
||||
}
|
||||
|
||||
report_peer_connected(&peer_addr, peer_addr_len);
|
||||
pthread_t the_thread;
|
||||
|
||||
thread_config_t* config = (thread_config_t*)malloc(sizeof(*config));
|
||||
if (!config) {
|
||||
die("OOM");
|
||||
}
|
||||
config->sockfd = newsockfd;
|
||||
pthread_create(&the_thread, NULL, server_thread, config);
|
||||
|
||||
// 回收线程 —— 在线程结束的时候,它占用的资源会被回收
|
||||
// 因为主线程在一直运行,所以它比服务线程存活更久。
|
||||
pthread_detach(the_thread);
|
||||
}
|
||||
```
|
||||
|
||||
这是 `server_thread` 函数:
|
||||
|
||||
```
|
||||
void* server_thread(void* arg) {
|
||||
thread_config_t* config = (thread_config_t*)arg;
|
||||
int sockfd = config->sockfd;
|
||||
free(config);
|
||||
|
||||
// This cast will work for Linux, but in general casting pthread_id to an 这个类型转换在 Linux 中可以正常运行,但是一般来说将 pthread_id 类型转换成整形不便于移植代码
|
||||
// integral type isn't portable.
|
||||
unsigned long id = (unsigned long)pthread_self();
|
||||
printf("Thread %lu created to handle connection with socket %d\n", id,
|
||||
sockfd);
|
||||
serve_connection(sockfd);
|
||||
printf("Thread %lu done\n", id);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
线程 “configuration” 是作为 `thread_config_t` 结构体进行传递的:
|
||||
|
||||
```
|
||||
typedef struct { int sockfd; } thread_config_t;
|
||||
```
|
||||
|
||||
主循环中调用的 `pthread_create` 产生一个新线程,然后运行 `server_thread` 函数。这个线程会在 `server_thread` 返回的时候结束。而在 `serve_connection` 返回的时候 `server_thread` 才会返回。`serve_connection` 和第一节完全一样。
|
||||
|
||||
第一节中我们用脚本生成了多个并发访问的客户端,观察服务器是怎么处理的。现在来看看多线程服务器的处理结果:
|
||||
|
||||
```
|
||||
$ python3.6 simple-client.py -n 3 localhost 9090
|
||||
INFO:2017-09-20 06:31:56,632:conn1 connected...
|
||||
INFO:2017-09-20 06:31:56,632:conn2 connected...
|
||||
INFO:2017-09-20 06:31:56,632:conn0 connected...
|
||||
INFO:2017-09-20 06:31:56,632:conn1 sending b'^abc$de^abte$f'
|
||||
INFO:2017-09-20 06:31:56,632:conn2 sending b'^abc$de^abte$f'
|
||||
INFO:2017-09-20 06:31:56,632:conn0 sending b'^abc$de^abte$f'
|
||||
INFO:2017-09-20 06:31:56,633:conn1 received b'b'
|
||||
INFO:2017-09-20 06:31:56,633:conn2 received b'b'
|
||||
INFO:2017-09-20 06:31:56,633:conn0 received b'b'
|
||||
INFO:2017-09-20 06:31:56,670:conn1 received b'cdbcuf'
|
||||
INFO:2017-09-20 06:31:56,671:conn0 received b'cdbcuf'
|
||||
INFO:2017-09-20 06:31:56,671:conn2 received b'cdbcuf'
|
||||
INFO:2017-09-20 06:31:57,634:conn1 sending b'xyz^123'
|
||||
INFO:2017-09-20 06:31:57,634:conn2 sending b'xyz^123'
|
||||
INFO:2017-09-20 06:31:57,634:conn1 received b'234'
|
||||
INFO:2017-09-20 06:31:57,634:conn0 sending b'xyz^123'
|
||||
INFO:2017-09-20 06:31:57,634:conn2 received b'234'
|
||||
INFO:2017-09-20 06:31:57,634:conn0 received b'234'
|
||||
INFO:2017-09-20 06:31:58,635:conn1 sending b'25$^ab0000$abab'
|
||||
INFO:2017-09-20 06:31:58,635:conn2 sending b'25$^ab0000$abab'
|
||||
INFO:2017-09-20 06:31:58,636:conn1 received b'36bc1111'
|
||||
INFO:2017-09-20 06:31:58,636:conn2 received b'36bc1111'
|
||||
INFO:2017-09-20 06:31:58,637:conn0 sending b'25$^ab0000$abab'
|
||||
INFO:2017-09-20 06:31:58,637:conn0 received b'36bc1111'
|
||||
INFO:2017-09-20 06:31:58,836:conn2 disconnecting
|
||||
INFO:2017-09-20 06:31:58,836:conn1 disconnecting
|
||||
INFO:2017-09-20 06:31:58,837:conn0 disconnecting
|
||||
```
|
||||
|
||||
实际上,所有客户端同时连接,它们与服务器的通信是同时发生的。
|
||||
|
||||
### 每个客户端一个线程的难点
|
||||
|
||||
尽管在现代操作系统中就资源利用率方面来看,线程相当的高效,但前一节中讲到的方法在高负载时却会出现纰漏。
|
||||
|
||||
想象一下这样的情景:很多客户端同时进行连接,某些会话持续的时间长。这意味着某个时刻服务器上有很多活跃的线程。太多的线程会消耗掉大量的内存和 CPU 资源,而仅仅是用于上下文切换^注1 。另外其也可视为安全问题:因为这样的设计容易让服务器成为 [DoS 攻击][14] 的目标 —— 上百万个客户端同时连接,并且客户端都处于闲置状态,这样耗尽了所有资源就可能让服务器宕机。
|
||||
|
||||
当服务器要与每个客户端通信,CPU 进行大量计算时,就会出现更严重的问题。这种情况下,容易想到的方法是减少服务器的响应能力 —— 只有其中一些客户端能得到服务器的响应。
|
||||
|
||||
因此,对多线程服务器所能够处理的并发客户端数做一些 _速率限制_ 就是个明智的选择。有很多方法可以实现。最容易想到的是计数当前已经连接上的客户端,把连接数限制在某个范围内(需要通过仔细的测试后决定)。另一种流行的多线程应用设计是使用 _线程池_。
|
||||
|
||||
### 线程池
|
||||
|
||||
[线程池][15] 很简单,也很有用。服务器创建几个任务线程,这些线程从某些队列中获取任务。这就是“池”。然后每一个客户端的连接被当成任务分发到池中。只要池中有空闲的线程,它就会去处理任务。如果当前池中所有线程都是繁忙状态,那么服务器就会阻塞,直到线程池可以接受任务(某个繁忙状态的线程处理完当前任务后,变回空闲的状态)。
|
||||
|
||||
这里有个 4 线程的线程池处理任务的图。任务(这里就是客户端的连接)要等到线程池中的某个线程可以接受新任务。
|
||||
|
||||

|
||||
|
||||
非常明显,线程池的定义就是一种按比例限制的机制。我们可以提前设定服务器所能拥有的线程数。那么这就是并发连接的最多的客户端数 —— 其它的客户端就要等到线程空闲。如果我们的池中有 8 个线程,那么 8 就是服务器可以处理的最多的客户端并发连接数,哪怕上千个客户端想要同时连接。
|
||||
|
||||
那么怎么确定池中需要有多少个线程呢?通过对问题范畴进行细致的分析、评估、实验以及根据我们拥有的硬件配置。如果是单核的云服务器,答案只有一个;如果是 100 核心的多套接字的服务器,那么答案就有很多种。也可以在运行时根据负载动态选择池的大小 —— 我会在这个系列之后的文章中谈到这个东西。
|
||||
|
||||
使用线程池的服务器在高负载情况下表现出 _性能退化_ —— 客户端能够以稳定的速率进行连接,可能会比其它时刻得到响应的用时稍微久一点;也就是说,无论多少个客户端同时进行连接,服务器总能保持响应,尽最大能力响应等待的客户端。与之相反,每个客户端一个线程的服务器,会接收多个客户端的连接直到过载,这时它更容易崩溃或者因为要处理_所有_客户端而变得缓慢,因为资源都被耗尽了(比如虚拟内存的占用)。
|
||||
|
||||
### 在服务器上使用线程池
|
||||
|
||||
为了[改变服务器的实现][16],我用了 Python,在 Python 的标准库中带有一个已经实现好的稳定的线程池。(`concurrent.futures` 模块里的 `ThreadPoolExecutor`) ^注2 。
|
||||
|
||||
服务器创建一个线程池,然后进入循环,监听套接字接收客户端的连接。用 `submit` 把每一个连接的客户端分配到池中:
|
||||
|
||||
```
|
||||
pool = ThreadPoolExecutor(args.n)
|
||||
sockobj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sockobj.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sockobj.bind(('localhost', args.port))
|
||||
sockobj.listen(15)
|
||||
|
||||
try:
|
||||
while True:
|
||||
client_socket, client_address = sockobj.accept()
|
||||
pool.submit(serve_connection, client_socket, client_address)
|
||||
except KeyboardInterrupt as e:
|
||||
print(e)
|
||||
sockobj.close()
|
||||
```
|
||||
|
||||
`serve_connection` 函数和 C 的那部分很像,与一个客户端交互,直到其断开连接,并且遵循我们的协议:
|
||||
|
||||
```
|
||||
ProcessingState = Enum('ProcessingState', 'WAIT_FOR_MSG IN_MSG')
|
||||
|
||||
def serve_connection(sockobj, client_address):
|
||||
print('{0} connected'.format(client_address))
|
||||
sockobj.sendall(b'*')
|
||||
state = ProcessingState.WAIT_FOR_MSG
|
||||
|
||||
while True:
|
||||
try:
|
||||
buf = sockobj.recv(1024)
|
||||
if not buf:
|
||||
break
|
||||
except IOError as e:
|
||||
break
|
||||
for b in buf:
|
||||
if state == ProcessingState.WAIT_FOR_MSG:
|
||||
if b == ord(b'^'):
|
||||
state = ProcessingState.IN_MSG
|
||||
elif state == ProcessingState.IN_MSG:
|
||||
if b == ord(b'$'):
|
||||
state = ProcessingState.WAIT_FOR_MSG
|
||||
else:
|
||||
sockobj.send(bytes([b + 1]))
|
||||
else:
|
||||
assert False
|
||||
|
||||
print('{0} done'.format(client_address))
|
||||
sys.stdout.flush()
|
||||
sockobj.close()
|
||||
```
|
||||
|
||||
来看看线程池的大小对并行访问的客户端的阻塞行为有什么样的影响。为了演示,我会运行一个池大小为 2 的线程池服务器(只生成两个线程用于响应客户端)。
|
||||
|
||||
```
|
||||
$ python3.6 threadpool-server.py -n 2
|
||||
```
|
||||
|
||||
在另外一个终端里,运行客户端模拟器,产生 3 个并发访问的客户端:
|
||||
|
||||
```
|
||||
$ python3.6 simple-client.py -n 3 localhost 9090
|
||||
INFO:2017-09-22 05:58:52,815:conn1 connected...
|
||||
INFO:2017-09-22 05:58:52,827:conn0 connected...
|
||||
INFO:2017-09-22 05:58:52,828:conn1 sending b'^abc$de^abte$f'
|
||||
INFO:2017-09-22 05:58:52,828:conn0 sending b'^abc$de^abte$f'
|
||||
INFO:2017-09-22 05:58:52,828:conn1 received b'b'
|
||||
INFO:2017-09-22 05:58:52,828:conn0 received b'b'
|
||||
INFO:2017-09-22 05:58:52,867:conn1 received b'cdbcuf'
|
||||
INFO:2017-09-22 05:58:52,867:conn0 received b'cdbcuf'
|
||||
INFO:2017-09-22 05:58:53,829:conn1 sending b'xyz^123'
|
||||
INFO:2017-09-22 05:58:53,829:conn0 sending b'xyz^123'
|
||||
INFO:2017-09-22 05:58:53,830:conn1 received b'234'
|
||||
INFO:2017-09-22 05:58:53,831:conn0 received b'2'
|
||||
INFO:2017-09-22 05:58:53,831:conn0 received b'34'
|
||||
INFO:2017-09-22 05:58:54,831:conn1 sending b'25$^ab0000$abab'
|
||||
INFO:2017-09-22 05:58:54,832:conn1 received b'36bc1111'
|
||||
INFO:2017-09-22 05:58:54,832:conn0 sending b'25$^ab0000$abab'
|
||||
INFO:2017-09-22 05:58:54,833:conn0 received b'36bc1111'
|
||||
INFO:2017-09-22 05:58:55,032:conn1 disconnecting
|
||||
INFO:2017-09-22 05:58:55,032:conn2 connected...
|
||||
INFO:2017-09-22 05:58:55,033:conn2 sending b'^abc$de^abte$f'
|
||||
INFO:2017-09-22 05:58:55,033:conn0 disconnecting
|
||||
INFO:2017-09-22 05:58:55,034:conn2 received b'b'
|
||||
INFO:2017-09-22 05:58:55,071:conn2 received b'cdbcuf'
|
||||
INFO:2017-09-22 05:58:56,036:conn2 sending b'xyz^123'
|
||||
INFO:2017-09-22 05:58:56,036:conn2 received b'234'
|
||||
INFO:2017-09-22 05:58:57,037:conn2 sending b'25$^ab0000$abab'
|
||||
INFO:2017-09-22 05:58:57,038:conn2 received b'36bc1111'
|
||||
INFO:2017-09-22 05:58:57,238:conn2 disconnecting
|
||||
```
|
||||
|
||||
回顾之前讨论的服务器行为:
|
||||
|
||||
1. 在顺序服务器中,所有的连接都是串行的。一个连接结束后,下一个连接才能开始。
|
||||
2. 前面讲到的每个客户端一个线程的服务器中,所有连接都被同时接受并得到服务。
|
||||
|
||||
这里可以看到一种可能的情况:两个连接同时得到服务,只有其中一个结束连接后第三个才能连接上。这就是把线程池大小设置成 2 的结果。真实用例中我们会把线程池设置的更大些,取决于机器和实际的协议。线程池的缓冲机制就能很好理解了 —— 我 [几个月前][18] 更详细的介绍过这种机制,关于 Clojure 的 `core.async` 模块。
|
||||
|
||||
### 总结与展望
|
||||
|
||||
这篇文章讨论了在服务器中,用多线程作并发的方法。每个客户端一个线程的方法最早提出来,但是实际上却不常用,因为它并不安全。
|
||||
|
||||
线程池就常见多了,最受欢迎的几个编程语言有良好的实现(某些编程语言,像 Python,就是在标准库中实现)。这里说的使用线程池的服务器,不会受到每个客户端一个线程的弊端。
|
||||
|
||||
然而,线程不是处理多个客户端并行访问的唯一方法。下一节中我们会看看其它的解决方案,可以使用_异步处理_,或者_事件驱动_的编程。
|
||||
|
||||
* * *
|
||||
|
||||
- 注1:老实说,现代 Linux 内核可以承受足够多的并发线程 —— 只要这些线程主要在 I/O 上被阻塞。[这里有个示例程序][2],它产生可配置数量的线程,线程在循环体中是休眠的,每 50 ms 唤醒一次。我在 4 核的 Linux 机器上可以轻松的产生 10000 个线程;哪怕这些线程大多数时间都在睡眠,它们仍然消耗一到两个核心,以便实现上下文切换。而且,它们占用了 80 GB 的虚拟内存(Linux 上每个线程的栈大小默认是 8MB)。实际使用中,线程会使用内存并且不会在循环体中休眠,因此它可以非常快的占用完一个机器的内存。
|
||||
- 注2:自己动手实现一个线程池是个有意思的练习,但我现在还不想做。我曾写过用来练手的 [针对特殊任务的线程池][4]。是用 Python 写的;用 C 重写的话有些难度,但对于经验丰富的程序员,几个小时就够了。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://eli.thegreenplace.net/2017/concurrent-servers-part-2-threads/
|
||||
|
||||
作者:[Eli Bendersky][a]
|
||||
译者:[GitFuture](https://github.com/GitFuture)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://eli.thegreenplace.net/pages/about
|
||||
[1]:https://eli.thegreenplace.net/2017/concurrent-servers-part-2-threads/#id1
|
||||
[2]:https://github.com/eliben/code-for-blog/blob/master/2017/async-socket-server/threadspammer.c
|
||||
[3]:https://eli.thegreenplace.net/2017/concurrent-servers-part-2-threads/#id2
|
||||
[4]:http://eli.thegreenplace.net/2011/12/27/python-threads-communication-and-stopping
|
||||
[5]:https://eli.thegreenplace.net/tag/concurrency
|
||||
[6]:https://eli.thegreenplace.net/tag/c-c
|
||||
[7]:https://eli.thegreenplace.net/tag/python
|
||||
[8]:https://linux.cn/article-8993-1.html
|
||||
[9]:http://eli.thegreenplace.net/2017/concurrent-servers-part-2-threads/
|
||||
[10]:http://eli.thegreenplace.net/2017/concurrent-servers-part-3-event-driven/
|
||||
[11]:https://github.com/eliben/code-for-blog/blob/master/2017/async-socket-server/threaded-server.c
|
||||
[12]:http://eli.thegreenplace.net/2010/04/05/pthreads-as-a-case-study-of-good-api-design
|
||||
[13]:https://eli.thegreenplace.net/2017/concurrent-servers-part-2-threads/#id3
|
||||
[14]:https://en.wikipedia.org/wiki/Denial-of-service_attack
|
||||
[15]:https://en.wikipedia.org/wiki/Thread_pool
|
||||
[16]:https://github.com/eliben/code-for-blog/blob/master/2017/async-socket-server/threadpool-server.py
|
||||
[17]:https://eli.thegreenplace.net/2017/concurrent-servers-part-2-threads/#id4
|
||||
[18]:http://eli.thegreenplace.net/2017/clojure-concurrency-and-blocking-with-coreasync/
|
||||
[19]:https://eli.thegreenplace.net/2017/concurrent-servers-part-2-threads/
|
||||
[20]:https://linux.cn/article-8993-1.html
|
||||
@@ -0,0 +1,92 @@
|
||||
如何在一个 U 盘上安装多个 Linux 发行版
|
||||
============================================================
|
||||
|
||||
> 概要:本教程介绍如何在一个 U 盘上安装多个 Linux 发行版。这样,你可以在单个 U 盘上享受多个<ruby>现场版<rt>live</rt></ruby> Linux 发行版了。
|
||||
|
||||
我喜欢通过 U 盘尝试不同的 Linux 发行版。它让我可以在真实的硬件上测试操作系统,而不是虚拟化的环境中。此外,我可以将 USB 插入任何系统(比如 Windows 系统),做任何我想要的事情,以及享受相同的 Linux 体验。而且,如果我的系统出现问题,我可以使用 U 盘恢复!
|
||||
|
||||
创建单个[可启动的现场版 Linux USB][8] 很简单,你只需下载一个 ISO 文件并将其刻录到 U 盘。但是,如果你想尝试多个 Linux 发行版呢?你可以使用多个 U 盘,也可以覆盖同一个 U 盘以尝试其他 Linux 发行版。但这两种方法都不是很方便。
|
||||
|
||||
那么,有没有在单个 U 盘上安装多个 Linux 发行版的方式呢?我们将在本教程中看到如何做到这一点。
|
||||
|
||||
### 如何创建有多个 Linux 发行版的可启动 USB
|
||||
|
||||

|
||||
|
||||
我们有一个工具正好可以做到_在单个 U 盘上保留多个 Linux 发行版_。你所需要做的只是选择要安装的发行版。在本教程中,我们将介绍_如何在 U 盘中安装多个 Linux 发行版_用于<ruby>现场会话<rt>live session</rt></ruby>。
|
||||
|
||||
要确保你有一个足够大的 U 盘,以便在它上面安装多个 Linux 发行版,一个 8 GB 的 U 盘应该足够用于三四个 Linux 发行版。
|
||||
|
||||
#### 步骤 1
|
||||
|
||||
[MultiBootUSB][9] 是一个自由、开源的跨平台应用程序,允许你创建具有多个 Linux 发行版的 U 盘。它还支持在任何时候卸载任何发行版,以便你回收驱动器上的空间用于另一个发行版。
|
||||
|
||||
下载 .deb 包并双击安装。
|
||||
|
||||
[下载 MultiBootUSB][10]
|
||||
|
||||
#### 步骤 2
|
||||
|
||||
推荐的文件系统是 FAT32,因此在创建多引导 U 盘之前,请确保格式化 U 盘。
|
||||
|
||||
#### 步骤 3
|
||||
|
||||
下载要安装的 Linux 发行版的 ISO 镜像。
|
||||
|
||||
#### 步骤 4
|
||||
|
||||
完成这些后,启动 MultiBootUSB。
|
||||
|
||||

|
||||
|
||||
主屏幕要求你选择 U 盘和你打算放到 U 盘上的 Linux 发行版镜像文件。
|
||||
|
||||
MultiBootUSB 支持 Ubuntu、Fedora 和 Debian 发行版的持久化,这意味着对 Linux 发行版的现场版本所做的更改将保存到 USB 上。
|
||||
|
||||
你可以通过拖动 MultiBootUSB 选项卡下的滑块来选择持久化大小。持久化为你提供了在运行时将更改保存到 U 盘的选项。
|
||||
|
||||

|
||||
|
||||
#### 步骤 5
|
||||
|
||||
单击“安装发行版”选项并继续安装。在显示成功的安装消息之前,需要一些时间才能完成。
|
||||
|
||||
你现在可以在已安装部分中看到发行版了。对于另外的操作系统,重复该过程。这是我安装 Ubuntu 16.10 和 Fedora 24 后的样子。
|
||||
|
||||

|
||||
|
||||
#### 步骤 6
|
||||
|
||||
下次通过 USB 启动时,我可以选择任何一个发行版。
|
||||
|
||||

|
||||
|
||||
只要你的 U 盘允许,你可以添加任意数量的发行版。要删除发行版,请从列表中选择它,然后单击卸载发行版。
|
||||
|
||||
### 最后的话
|
||||
|
||||
MultiBootUSB 真的很便于在 U 盘上安装多个 Linux 发行版。只需点击几下,我就有两个我最喜欢的操作系统的工作盘了,我可以在任何系统上启动它们。
|
||||
|
||||
如果你在安装或使用 MultiBootUSB 时遇到任何问题,请在评论中告诉我们。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/multiple-linux-one-usb/
|
||||
|
||||
作者:[Ambarish Kumar][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://itsfoss.com/author/ambarish/
|
||||
[1]:https://itsfoss.com/author/ambarish/
|
||||
[2]:https://itsfoss.com/multiple-linux-one-usb/#comments
|
||||
[3]:https://www.facebook.com/share.php?u=https%3A%2F%2Fitsfoss.com%2Fmultiple-linux-one-usb%2F%3Futm_source%3Dfacebook%26utm_medium%3Dsocial%26utm_campaign%3DSocialWarfare
|
||||
[4]:https://twitter.com/share?original_referer=/&text=How+to+Install+Multiple+Linux+Distributions+on+One+USB&url=https://itsfoss.com/multiple-linux-one-usb/%3Futm_source%3Dtwitter%26utm_medium%3Dsocial%26utm_campaign%3DSocialWarfare&via=itsfoss2
|
||||
[5]:https://plus.google.com/share?url=https%3A%2F%2Fitsfoss.com%2Fmultiple-linux-one-usb%2F%3Futm_source%3DgooglePlus%26utm_medium%3Dsocial%26utm_campaign%3DSocialWarfare
|
||||
[6]:https://www.linkedin.com/cws/share?url=https%3A%2F%2Fitsfoss.com%2Fmultiple-linux-one-usb%2F%3Futm_source%3DlinkedIn%26utm_medium%3Dsocial%26utm_campaign%3DSocialWarfare
|
||||
[7]:https://www.reddit.com/submit?url=https://itsfoss.com/multiple-linux-one-usb/&title=How+to+Install+Multiple+Linux+Distributions+on+One+USB
|
||||
[8]:https://itsfoss.com/create-live-usb-of-ubuntu-in-windows/
|
||||
[9]:http://multibootusb.org/
|
||||
[10]:https://github.com/mbusb/multibootusb/releases/download/v8.8.0/python3-multibootusb_8.8.0-1_all.deb
|
||||
@@ -0,0 +1,58 @@
|
||||
OpenMessaging:构建一个分布式消息分发开放标准
|
||||
============================================================
|
||||
|
||||
|
||||
通过在云计算、大数据和标准 API 上的企业及社区的协作,我很高兴 OpenMessaging 项目进入 Linux 基金会。OpenMessaging 社区的目标是为分布式消息分发创建全球采用的、供应商中立的和开放标准,可以部署在云端、内部和混合云情景中。
|
||||
|
||||
阿里巴巴、雅虎、滴滴和 Streamlio 是该项目的创始贡献者。Linux 基金会已与这个初始项目社区合作来建立一个治理模式和结构,以实现运作在消息 API 标准上的生态系统的长期受益。
|
||||
|
||||
由于越来越多的公司和开发者迈向<ruby>云原生应用<rt>cloud native application</rt></ruby>,消息式应用和流式应用的扩展面临的挑战也在不断发展。这包括平台之间的互操作性问题,<ruby>[线路级协议](https://en.wikipedia.org/wiki/Wire_protocol)<rt>wire-level protocol</rt></ruby>之间缺乏兼容性以及系统间缺乏标准的基准测试。
|
||||
|
||||
特别是当数据跨不同的消息平台和流平台进行传输时会出现兼容性问题,这意味着额外的工作和维护成本。现有解决方案缺乏负载平衡、容错、管理、安全性和流功能的标准化指南。目前的系统不能满足现代面向云的消息应用和流应用的需求。这可能导致开发人员额外的工作,并且难以或不可能满足物联网、边缘计算、智能城市等方面的尖端业务需求。
|
||||
|
||||
OpenMessaging 的贡献者正在寻求通过以下方式改进分布式消息分发:
|
||||
|
||||
* 为分布式消息分发创建一个面向全球、面向云、供应商中立的行业标准
|
||||
* 促进用于测试应用程序的标准基准发展
|
||||
* 支持平台独立
|
||||
* 以可伸缩性、灵活性、隔离和安全性为目标的云数据的流和消息分发要求
|
||||
* 培育不断发展的开发贡献者社区
|
||||
|
||||
你可以在这了解有关新项目的更多信息以及如何参与: [http://openmessaging.cloud][1]。
|
||||
|
||||
这些是支持 OpenMessaging 的一些组织:
|
||||
|
||||
“我们多年来一直专注于消息分发和流领域,在此期间,我们探索了 Corba 通知、JMS 和其它标准,来试图解决我们最严格的业务需求。阿里巴巴在评估了可用的替代品后,选择创建一个新的面向云的消息分发标准 OpenMessaging,这是一个供应商中立,且语言无关的标准,并为金融、电子商务、物联网和大数据等领域提供了行业指南。此外,它目地在于跨异构系统和平台间开发消息分发和流应用。我们希望它可以是开放、简单、可扩展和可互操作的。另外,我们要根据这个标准建立一个生态系统,如基准测试、计算和各种连接器。我们希望有新的贡献,并希望大家能够共同努力,推动 OpenMessaging 标准的发展。”
|
||||
|
||||
——阿里巴巴高级架构师 Von Gosling,Apache RocketMQ 的联合创始人,以及 OpenMessaging 的原始发起人
|
||||
|
||||
“随着应用程序消息的复杂性和规模的不断扩大,缺乏标准的接口为开发人员和组织带来了复杂性和灵活性的障碍。Streamlio 很高兴与其他领导者合作推出 OpenMessaging 标准倡议来给客户一个轻松使用高性能、低延迟的消息传递解决方案,如 Apache Pulsar,它提供了企业所需的耐用性、一致性和可用性。“
|
||||
|
||||
—— Streamlio 的软件工程师、Apache Pulsar 的联合创始人以及 Apache BookKeeper PMC 的成员 Matteo Merli
|
||||
|
||||
“Oath(Verizon 旗下领先的媒体和技术品牌,包括雅虎和 AOL)支持开放,协作的举措,并且很乐意加入 OpenMessaging 项目。”
|
||||
|
||||
—— Joe Francis,核心平台总监
|
||||
|
||||
“在滴滴中,我们定义了一组私有的生产者 API 和消费者 API 来隐藏开源的 MQ(如 Apache Kafka、Apache RocketMQ 等)之间的差异,并提供额外的自定义功能。我们计划将这些发布到开源社区。到目前为止,我们已经积累了很多关于 MQ 和 API 统一的经验,并愿意在 OpenMessaging 中与其它 API 一起构建 API 的共同标准。我们真诚地认为,统一和广泛接受的 API 标准可以使 MQ 技术和依赖于它的应用程序受益。”
|
||||
|
||||
—— 滴滴的架构师 Neil Qi_
|
||||
|
||||
“有许多不同的开源消息分发解决方案,包括 Apache ActiveMQ、Apache RocketMQ、Apache Pulsar 和 Apache Kafka。缺乏行业级的可扩展消息分发标准使得评估合适的解决方案变得困难。我们很高兴能够与多个开源项目共同努力,共同确定可扩展的开放消息规范。 Apache BookKeeper 已成功在雅虎(通过 Apache Pulsar)和 Twitter(通过 Apache DistributedLog)的生产环境中部署,它作为其企业级消息系统的持久化、高性能、低延迟存储基础。我们很高兴加入 OpenMessaging 帮助其它项目解决诸如低延迟持久化、一致性和可用性等在消息分发方案中的常见问题。”
|
||||
|
||||
—— Streamlio 的联合创始人、Apache BookKeeper 的 PMC 主席、Apache DistributedLog 的联合创造者,Sijie Guo
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.linuxfoundation.org/blog/building-open-standard-distributed-messaging-introducing-openmessaging/
|
||||
|
||||
作者:[Mike Dolan][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.linuxfoundation.org/author/mdolan/
|
||||
[1]:http://openmessaging.cloud/
|
||||
[2]:https://www.linuxfoundation.org/author/mdolan/
|
||||
[3]:https://www.linuxfoundation.org/category/blog/
|
||||
@@ -0,0 +1,32 @@
|
||||
密码修改最佳实践
|
||||
=============
|
||||
|
||||
NIST 最近发表了四卷 [SP800-63b 数字身份指南][3]。除此之外,它还对密码提供三个重要的建议:
|
||||
|
||||
1. 不要再纠结于复杂的密码规则。它们使密码难以记住。因为人为的复杂密码很难输入,因此增加了错误。它们[也没有很大帮助][1]。最好让人们使用密码短语。
|
||||
2. 停止密码到期。这是[我们以前使用计算机的一个老的想法][2]。如今不要让人改变密码,除非有泄密的迹象。
|
||||
3. 让人们使用密码管理器。这就是处理我们所有密码的方式。
|
||||
|
||||
这些密码规则不能[让用户安全][4]。让系统安全才是最重要的。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
自从 2004 年以来,我一直在博客上写关于安全的文章,以及从 1998 年以来我的每月订阅中也有。我写书、文章和学术论文。目前我是 IBM Resilient 的首席技术官,哈佛伯克曼中心的研究员,EFF 的董事会成员。
|
||||
|
||||
-----------------
|
||||
|
||||
via: https://www.schneier.com/blog/archives/2017/10/changes_in_pass.html
|
||||
|
||||
作者:[Bruce Schneier][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.schneier.com/blog/about/
|
||||
[1]:https://www.wsj.com/articles/the-man-who-wrote-those-password-rules-has-a-new-tip-n3v-r-m1-d-1502124118
|
||||
[2]:https://securingthehuman.sans.org/blog/2017/03/23/time-for-password-expiration-to-die
|
||||
[3]:http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf
|
||||
[4]:http://ieeexplore.ieee.org/document/7676198/?reload=true
|
||||
@@ -0,0 +1,51 @@
|
||||
见多识广的 Pornhub 人工智能比你认识更多的 XXX 明星
|
||||
============================================================
|
||||
|
||||

|
||||
|
||||
你有没有想过,之所以能够根据自己不同兴趣的组合搜索到需要的视频,是因为有那些每日浏览无数视频内容且对它们进行分类和标记的可怜人存在,然而这些看不见的英雄们却在人工智能面前变得英雄无用武之地。
|
||||
|
||||
世界上最大的 XXX 电影分享网站 Pornhub 宣布,它将推出新的 AI 模型,利用计算机视觉技术自动检测和识别 XXX 明星的名字。
|
||||
|
||||
根据 X-rated 网站的消息,目前该算法经过训练后已经通过简单的扫描和对镜头的理解,可以识别超过 1 万名 XXX 明星。Pornhub 说,通过向此 AI 模型输入数千个视频和 XXX 明星的正式照片,以让它学习如何返回准确的名字。
|
||||
|
||||
为了减小错误,这个成人网站将向用户求证由 AI 提供的标签和分类是否合适。用户可以根据结果的准确度,提出支持或是反对。这将会让算法变得更加智能。
|
||||
|
||||

|
||||
|
||||
“现在,用户可以根据自身喜好寻找指定的 XXX 明星,我们也能够返回给用户尽可能精确的搜索结果,” PornHub 副总裁 Corey Price 说。“毫无疑问,我们的模型也将在未来的发展中扮演关键角色,尤其是考虑到每天有超过 1 万个的视频添加到网站上。”
|
||||
|
||||
“事实上,在过去的一个月里,我们测试了这个模型的测试版本,它(每天)可以扫描 5 万段视频,并且向视频添加或者移除标签。”
|
||||
|
||||
除了识别表演者,该算法还能区分不同类别的内容:比如在 “Public” 类别下的是户外拍摄的视频,以及 “Blonde” 类别下的视频应该至少有名金发女郎。
|
||||
|
||||
XXX 公司计划明年在 AI 模型的帮助下,对全部 500 万个视频编目,希望能让用户更容易找到与他们的期望最接近的视频片段。
|
||||
|
||||
早先就有研究人员借助计算机视觉算法对 XXX 电影进行描述。之前就有一名开发者使用微软的人工智能技术来构建这个机器人,它可以整天[观察和解读][2]各种内容。
|
||||
|
||||
Pornhub 似乎让这一想法更进一步,这些那些遍布全球的视频审看员的噩梦。
|
||||
|
||||
虽然人工智能被发展到这个方面可能会让你感觉有些不可思议,但 XXX 业因其对搜索引擎优化技术的狂热追求而[闻名][3]。
|
||||
|
||||
事实上,[成人内容服务][4]一直以来都[有广泛市场][5],且[受众不分年龄][6],这也是这些公司盈利的重要组成部分。
|
||||
|
||||
但是,这些每日阅片无数、兢兢业业为其分类的人们可能很快就会成为自动化威胁的牺牲品。但从好的一面看,他们终于有机会坐下来[让自动化为他们工作][7]。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://thenextweb.com/artificial-intelligence/2017/10/11/pornhub-ai-watch-tag/
|
||||
|
||||
作者:[MIX][a]
|
||||
译者:[东风唯笑](https://github.com/dongfengweixiao)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://thenextweb.com/author/dimitarmihov/
|
||||
[1]:https://thenextweb.com/author/dimitarmihov/
|
||||
[2]:https://thenextweb.com/shareables/2017/03/03/porn-bot-microsoft-ai-pornhub/?amp=1
|
||||
[3]:https://moz.com/ugc/yes-dear-there-is-porn-seo-and-we-can-learn-a-lot-from-it
|
||||
[4]:https://www.upwork.com/job/Native-English-Speaker-Required-For-Video-Titles-Descriptions_~0170f127db07b9232b/
|
||||
[5]:https://www.quora.com/How-do-adult-sites-practice-SEO
|
||||
[6]:https://www.blackhatworld.com/seo/adult-looking-for-content-writer-for-porn-site.502731/
|
||||
[7]:https://thenextweb.com/gear/2017/07/07/fleshlight-launch-review-masturbation/?amp=1
|
||||
@@ -0,0 +1,148 @@
|
||||
NixOS Linux: 先配置后安装的 Linux
|
||||
============================================================
|
||||
|
||||

|
||||
|
||||
配置是成功安装 NixOS 的关键。
|
||||
|
||||
我用 Linux 有些年头了。在这些年里我很有幸见证了开源的发展。各色各样的发行版在安装方面的努力,也是其中的一个比较独特的部分。以前,安装 Linux 是个最好让有技术的人来干的任务。现在,只要你会装软件,你就会安装 Linux。简单,并且,不是我吹,在吸引新用户方面效果拔群。事实上安装整个 Linux 操作系统都要比 Windows 用户安装个更新看起来要快一点。
|
||||
|
||||
但每一次,我都喜欢看到一些不同的东西——那些可以让我体验新鲜的东西。[NixOS][9] 在这方面就做的别具一格。讲真,我原来也就把它当作另一个提供标准特性和 KDE Plasma 5 界面的 Linux 发行版。
|
||||
|
||||
好像也没什么不对。
|
||||
|
||||
[下载 ISO 映像][10]后,我启动了 [VirtualBox][11] 并用下载的镜像创建了个新的虚拟机。VM 启动后,出来的是 Bash 的登录界面,界面上指导我用空密码去登录 root 账号,以及我该如何启动 GUI 显示管理器(图 1)。
|
||||
|
||||

|
||||
|
||||
*图 1: 与 NixOS 的初次接触可能不是太和谐。*
|
||||
|
||||
“好吧”我这样想着,“打开看看吧!”
|
||||
|
||||
GUI 启动和运行时(KDE Plasma 5),我没找到喜闻乐见的“安装”按钮。原来,NixOS 是一个在安装前需要你配置的发行版,真有趣。那就让我们瞧瞧它是如何做到的吧!
|
||||
|
||||
### 安装前配置
|
||||
|
||||
你需要做的第一件事是建分区。由于 NixOS 安装程序不包含分区工具,你得用自带的 GParted (图 2)来创建一个 EXT4 分区。
|
||||
|
||||

|
||||
|
||||
*图 2: 安装前对磁盘分区。*
|
||||
|
||||
创建好分区,然后用命令 `mount /dev/sdX /mnt` 挂载。(请自行替换 `sdX` 为你新创建的分区)。
|
||||
|
||||
你现在需要创建一个配置文件。命令如下:
|
||||
|
||||
```
|
||||
nixos-generate-config --root /mnt
|
||||
```
|
||||
|
||||
上面的命令会创建两个文件(存放在目录 `/mnt/etc/nixos` 中):
|
||||
|
||||
* `configuration.nix` — 默认配置文件。
|
||||
* `hardware-configuration.nix` — 硬件配置(无法编辑)
|
||||
|
||||
通过命令 `nano /mnt/etc/nixos/configuration.nix` 打开文件。其中有一些需要编辑的地方得注意。第一个改动便是设置启动选项。找到行:
|
||||
|
||||
```
|
||||
# boot.loader.grub.device = "/dev/sda"; # 或 efi 时用 "nodev"
|
||||
```
|
||||
|
||||
删除行首的 `#` 使该选项生效(确保 `/dev/sda` 与你新建的分区)。
|
||||
|
||||
通过配置文件,你可以设置时区和追加要安装的软件包。来看一个被注释掉的安装包的示例:
|
||||
|
||||
```
|
||||
# List packages installed in system profile. To search by name, run:
|
||||
# nix-env -aqP | grep wget
|
||||
# environment.systemPackages = with pkgs; [
|
||||
# wget vim
|
||||
# ];
|
||||
```
|
||||
|
||||
如果你想要添加软件包,并在安装时安装它们,那就取消掉这段注释,并添加你需要的软件包。举个例子,比方说你要把 LibreOffice 加进去。示例详见下方:
|
||||
|
||||
```
|
||||
# List packages installed in system profile. To search by name, run:
|
||||
nix-env -aqP | grep wget
|
||||
environment.systemPackages = with pkgs; [
|
||||
libreoffice wget vim
|
||||
];
|
||||
```
|
||||
|
||||
你可以通过输入命令 `nix-env -aqP | grep PACKAGENAME` 来寻找确切的包名(`PACKAGENAME` 为你想要找的软件包)。如果你不想输命令,你也可以检索 [NixOS 的软件包数据库][12]。
|
||||
|
||||
在你把所有的软件包都添加完后,你还有件事儿需要做(如果你想要登录到桌面的话,我觉得你还得折腾下 KDE Plasma 5 桌面)。翻到配置文件的末尾并在最后的 `}` 符号前,追加如下内容:
|
||||
|
||||
```
|
||||
services.xserver = {
|
||||
enable = true;
|
||||
displayManager.sddm.enable = true;
|
||||
desktopManager.plasma5.enable = true;
|
||||
};
|
||||
```
|
||||
|
||||
在 [NixOS 官方文件][13] 中,你能找到配置文件中更多的选项。保存并关掉配置文件。
|
||||
|
||||
### 安装
|
||||
|
||||
在你按照自己的需求完善好配置之后,使用命令(需要 root 权限) `nixos-install`。完成安装所需要的时间,会随着你加入的软件包多少有所区别。安装结束后,你可以使用命令重启系统,(重启之后)迎接你的就是 KDE Plasma 5 的登录管理界面了(图 3)。
|
||||
|
||||

|
||||
|
||||
*图 3: KDE Plasma 5 登录管理界面*
|
||||
|
||||
### 安装后
|
||||
|
||||
你要首先要做的两件事之一便是给 root 用户设个密码(通过输入命令 `passwd` 来修改默认的密码),以及添加一个标准用户。做法和其它的 Linux 发行版无二。用 root 用户登录,然后在终端输入命令:
|
||||
|
||||
```
|
||||
useradd -m USER
|
||||
```
|
||||
|
||||
将 `USER` 替换成你想要添加的用户名。然后通过下面的命令给用户设上密码:
|
||||
|
||||
```
|
||||
passwd USER
|
||||
```
|
||||
|
||||
同样的将 `USER` 替换成你添加的用户。
|
||||
|
||||
然后会有提示引导你填写并验证新密码。然后,你就能用标准用户登录 NixOS 啦。
|
||||
|
||||
NixOS 在你安装并运行后,你可以为系统添加新的软件包,但并非通过寻常的方式。如果你发现你需要安装些新东西,你得回到配置文件(位置就是 `/etc/nixos/` ),找到之前安装时添加软件包的位置,运行以下命令(需要 root 权限):
|
||||
|
||||
```
|
||||
nixos-rebuild switch
|
||||
```
|
||||
|
||||
命令执行结束后,你就能使用新安装的软件包了。
|
||||
|
||||
### Enjoy NixOS
|
||||
|
||||
现在,NixOS 已经带着所有你想安装的软件和 KDE Plasma 5 桌面运行起来了。要知道,你所做的不仅仅只是安装了个 Linux 发行版,关键是你自定义出来的发行版非常符合你的需求。所以好好享受你的 NixOS 吧!
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.linux.com/learn/intro-to-linux/2017/10/nixos-linux-lets-you-configure-your-os-installing
|
||||
|
||||
作者:[JACK WALLEN][a]
|
||||
译者:[martin2011qi](https://github.com/martin2011qi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.linux.com/users/jlwallen
|
||||
[1]:https://www.linux.com/licenses/category/used-permission
|
||||
[2]:https://www.linux.com/licenses/category/used-permission
|
||||
[3]:https://www.linux.com/licenses/category/used-permission
|
||||
[4]:https://www.linux.com/licenses/category/creative-commons-zero
|
||||
[5]:https://www.linux.com/files/images/nixos1jpg
|
||||
[6]:https://www.linux.com/files/images/nixos2jpg
|
||||
[7]:https://www.linux.com/files/images/nixos3jpg
|
||||
[8]:https://www.linux.com/files/images/configurationjpg
|
||||
[9]:https://nixos.org/
|
||||
[10]:https://nixos.org/nixos/download.html
|
||||
[11]:https://www.virtualbox.org/wiki/Downloads
|
||||
[12]:https://nixos.org/nixos/packages.html
|
||||
[13]:https://nixos.org/nixos/manual/index.html#ch-configuration
|
||||
@@ -0,0 +1,57 @@
|
||||
Grafeas:旨在更好地审计容器
|
||||
============================================================
|
||||
|
||||
> Google 的 Grafeas 为容器的元数据提供了一个从镜像、构建细节到安全漏洞的通用 API。
|
||||
|
||||

|
||||
Thinkstock
|
||||
|
||||
我们运行的软件从来没有比今天更难获得。它分散在本地部署和云服务之间,由不知到有多少的开源组件构建而成,以快速的时间表交付,因此保证安全和质量变成了一个挑战。
|
||||
|
||||
最终的结果是软件难以审计、推断、安全化和管理。困难的不只是知道 VM 或容器是用什么构建的, 而是由谁来添加、删除或更改的。[Grafeas][5] 最初由 Google 设计,旨在使这些问题更容易解决。
|
||||
|
||||
### 什么是 Grafeas?
|
||||
|
||||
Grafeas 是一个定义软件组件的元数据 API 的开源项目。旨在提供一个统一的元数据模式,允许 VM、容器、JAR 文件和其他软件<ruby>工件<rt>artifact</rt></ruby>描述自己的运行环境以及管理它们的用户。目标是允许像在给定环境中使用的软件一样的审计,以及对该软件所做的更改的审计,并以一致和可靠的方式进行。
|
||||
|
||||
Grafeas提供两种格式的元数据 API —— 备注和事件:
|
||||
|
||||
* <ruby>备注<rt>note</rt></ruby>是有关软件工件的某些方面的细节。可以是已知软件漏洞的描述,有关如何构建软件的详细信息(构建器版本、校验和等),部署历史等。
|
||||
* <ruby>事件<rt>occurrence</rt></ruby>是备注的实例,包含了它们创建的地方和方式的细节。例如,已知软件漏洞的详细信息可能会有描述哪个漏洞扫描程序检测到它的情况、何时被检测到的事件信息,以及该漏洞是否被解决。
|
||||
|
||||
备注和事件都存储在仓库中。每个备注和事件都使用标识符进行跟踪,该标识符区分它并使其唯一。
|
||||
|
||||
Grafeas 规范包括备注类型的几个基本模式。例如,软件包漏洞模式描述了如何存储 CVE 或漏洞描述的备注信息。现在没有接受新模式类型的正式流程,但是[这已经在计划][6]创建这样一个流程。
|
||||
|
||||
### Grafeas 客户端和第三方支持
|
||||
|
||||
现在,Grafeas 主要作为规范和参考形式存在,它在 [GitHub 上提供][7]。 [Go][8]、[Python][9] 和 [Java][10] 的客户端都可以[用 Swagger 生成][11],所以其他语言的客户端也应该不难写出来。
|
||||
|
||||
Google 计划让 Grafeas 广泛使用的主要方式是通过 Kubernetes。 Kubernetes 的一个名为 Kritis 的策略引擎,可以根据 Grafeas 元数据对容器采取措施。
|
||||
|
||||
除 Google 之外的几家公司已经宣布计划将 Grafeas 的支持添加到现有产品中。例如,CoreOS 正在考察 Grafeas 如何与 Tectonic 集成,[Red Hat][12] 和 [IBM][13] 都计划在其容器产品和服务中添加 Grafeas 集成。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.infoworld.com/article/3230462/security/what-is-grafeas-better-auditing-for-containers.html
|
||||
|
||||
作者:[Serdar Yegulalp][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.infoworld.com/author/Serdar-Yegulalp/
|
||||
[1]:https://www.infoworld.com/author/Serdar-Yegulalp/
|
||||
[2]:https://www.infoworld.com/author/Serdar-Yegulalp/
|
||||
[3]:https://www.infoworld.com/article/3207686/cloud-computing/how-to-get-started-with-kubernetes.html#tk.ifw-infsb
|
||||
[4]:https://www.infoworld.com/newsletters/signup.html#tk.ifw-infsb
|
||||
[5]:http://grafeas.io/
|
||||
[6]:https://github.com/Grafeas/Grafeas/issues/38
|
||||
[7]:https://github.com/grafeas/grafeas
|
||||
[8]:https://github.com/Grafeas/client-go
|
||||
[9]:https://github.com/Grafeas/client-python
|
||||
[10]:https://github.com/Grafeas/client-java
|
||||
[11]:https://www.infoworld.com/article/2902750/application-development/manage-apis-with-swagger.html
|
||||
[12]:https://www.redhat.com/en/blog/red-hat-google-cloud-and-other-industry-leaders-join-together-standardize-kubernetes-service-component-auditing-and-policy-enforcement
|
||||
[13]:https://developer.ibm.com/dwblog/2017/grafeas/
|
||||
@@ -0,0 +1,53 @@
|
||||
如何成规模地部署多云的无服务器程序和 Cloud Foundry API
|
||||
============================================================
|
||||
|
||||
> IBM 的 Ken Parmelee 说:“微服务和 API 是产品,我们需要以这种方式思考。”
|
||||
|
||||
领导 IBM 的 API 网关和 Big Blue 开源项目的的 Ken Parmelee 对以开源方式 “进攻” API 以及如何创建微服务和使其伸缩有一些思考。
|
||||
|
||||
Parmelee 说:“微服务和 API 是产品,我们需要以这种方式思考这些问题。当你开始这么做,人们依赖它作为它们业务的一部分。这是你在这个领域所做的关键方面。”
|
||||
|
||||

|
||||
|
||||
他在最近的[北欧 APIs 2017 平台峰会][3]登上讲台,并挑战了一些流行的观念。
|
||||
|
||||
“快速失败不是一个很好的概念。你想在第一场比赛中获得一些非常棒的东西。这并不意味着你需要花费大量的时间,而是应该让它变得非常棒,然后不断的发展和改进。如果一开始真的很糟糕,人们就不会想要用你。”
|
||||
|
||||
他谈及包括 [OpenWhisk][4] 在内的 IBM 现代无服务器架构,这是一个 IBM 和 Apache 之间的开源伙伴关系。 云优先的基于分布式事件的编程服务是这两年多来重点关注这个领域的成果;IBM 是该领域领先的贡献者,它是 IBM 云服务的基础。它提供基础设施即服务(IaaS)、自动缩放、为多种语言提供支持、用户只需支付实际使用费用即可。这次旅程充满了挑战,因为他们发现服务器操作需要安全、并且需要轻松 —— 匿名访问、缺少使用路径、固定的 URL 格式等。
|
||||
|
||||
任何人都可以在 30 秒内在 [https://console.bluemix.net/openwhisk/][5] 上尝试这些无服务器 API。“这听起来很有噱头,但这是很容易做到的。我们正在结合 [Cloud Foundry 中完成的工作][6],并在 OpenWhisk 下的 Bluemix 中发布了它们,以提供安全性和可扩展性。”
|
||||
|
||||
他说:“灵活性对于微服务也是非常重要的。 当你使用 API 在现实世界中工作时,你开始需要跨云进行扩展。”这意味着从你的内部云走向公共云,并且“对你要怎么做有一个实在的概念很重要”。
|
||||
|
||||

|
||||
|
||||
在思考“任何云概念”的时候,他警告说,不是“将其放入一个 Docker 容器,并到处运行。这很棒,但需要在这些环境中有效运行。Docker 和 Kubernetes 有提供了很多帮助,但是你想要你的操作方式付诸实施。” 提前考虑 API 的使用,无论是在内部运行还是扩展到公有云并可以公开调用 - 你需要有这样的“架构观”,他补充道。
|
||||
|
||||
Parmelee 说:“我们都希望我们所创造的有价值,并被广泛使用。” API 越成功,将其提升到更高水平的挑战就越大。
|
||||
|
||||

|
||||
|
||||
*API 是微服务或“服务间”的组成部分。*
|
||||
|
||||
他说,API 的未来是原生云的 - 无论你从哪里开始。关键因素是可扩展性,简化后端管理,降低成本,避免厂商锁定。
|
||||
|
||||
你可以在下面或在 [YouTube][7] 观看他整整 23 分钟的演讲。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: http://superuser.openstack.org/articles/deploy-multi-cloud-serverless-cloud-foundry-apis-scale/
|
||||
|
||||
作者:[Superuser][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:http://superuser.openstack.org/articles/author/superuser/
|
||||
[1]:http://superuser.openstack.org/articles/author/superuser/
|
||||
[2]:http://superuser.openstack.org/articles/deploy-multi-cloud-serverless-cloud-foundry-apis-scale/
|
||||
[3]:https://nordicapis.com/events/the-2017-api-platform-summit/
|
||||
[4]:https://developer.ibm.com/openwhisk/
|
||||
[5]:https://console.bluemix.net/openwhisk/
|
||||
[6]:https://cloudfoundry.org/the-foundry/ibm-cloud/
|
||||
[7]:https://www.youtube.com/jA25Kmxr6fU
|
||||
@@ -0,0 +1,41 @@
|
||||
Linus Torvalds 说针对性的模糊测试正提升 Linux 安全性
|
||||
============================================================
|
||||
|
||||
Linux 4.14 发布候选第五版已经出来。Linus Torvalds 说:“可以去测试了。”
|
||||
|
||||

|
||||
|
||||
随着宣布推出 Linux 内核 4.14 的第五个候选版本,Linus Torvalds 表示<ruby>模糊测试<rt>fuzzing</rt></ruby>正产生一系列稳定的安全更新。
|
||||
|
||||
模糊测试通过产生随机代码来引发错误来对系统进行压力测试,从而有助于识别潜在的安全漏洞。模糊测试可以帮助软件开发人员在向用户发布软件之前捕获错误。
|
||||
|
||||
Google 使用各种模糊测试工具来查找它及其它供应商软件中的错误。微软推出了 [Project Springfield][1] 模糊测试服务,它能让企业客户测试自己的软件。
|
||||
|
||||
正如 Torvalds 指出的那样,Linux 内核开发人员从一开始就一直在使用模糊测试流程,例如 1991 年发布的工具 “crashme”,它在近 20 年后被 [Google 安全研究员 Tavis Ormandy ][2] 用来测试在虚拟机中处理不受信任的数据时,宿主机是否受到良好保护。
|
||||
|
||||
Torvalds [说][3]:“另外值得一提的是人们做了多少随机化模糊测试,而且这正在发现东西。”
|
||||
|
||||
“我们一直在做模糊测试(谁还记得只是生成随机代码,并跳转过去的老 “crashme” 程序?我们过去很早就这样做),人们在驱动子系统等方面做了一些很好的针对性模糊测试,而且已经有了各种各样的修复(不仅仅是上周的这些)。很高兴可以看到。”
|
||||
|
||||
Torvalds 提到,到目前为止,4.14 的发展“比预想的要麻烦一些”,但现在已经好了,并且在这个版本已经跑通了一些针对 x86 系统以及 AMD 芯片系统的修复。还有几个驱动程序、核心内核组件和工具的更新。
|
||||
|
||||
如前[所述][4],Linux 4.14 是 2017 年的长期稳定版本,迄今为止,它引入了核心内存管理功能、设备驱动程序更新以及文档、架构、文件系统、网络和工具的修改。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: http://www.zdnet.com/article/linus-torvalds-says-targeted-fuzzing-is-improving-linux-security/
|
||||
|
||||
作者:[Liam Tung][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:http://www.zdnet.com/meet-the-team/eu/liam-tung/
|
||||
[1]:http://www.zdnet.com/article/microsoft-seeks-testers-for-project-springfield-bug-detection-service/
|
||||
[2]:http://taviso.decsystem.org/virtsec.pdf
|
||||
[3]:http://lkml.iu.edu/hypermail/linux/kernel/1710.1/06454.html
|
||||
[4]:http://www.zdnet.com/article/first-linux-4-14-release-adds-very-core-features-arrives-in-time-for-kernels-26th-birthday/
|
||||
[5]:http://www.zdnet.com/meet-the-team/eu/liam-tung/
|
||||
[6]:http://www.zdnet.com/meet-the-team/eu/liam-tung/
|
||||
[7]:http://www.zdnet.com/topic/security/
|
||||
73
published/201710/20171017 PingCAP Launches TiDB 1.0.md
Normal file
73
published/201710/20171017 PingCAP Launches TiDB 1.0.md
Normal file
@@ -0,0 +1,73 @@
|
||||
PingCAP 推出 TiDB 1.0
|
||||
============================================================
|
||||
|
||||
> PingCAP 推出了 TiDB 1.0,一个可扩展的混合数据库解决方案
|
||||
|
||||
2017 年 10 月 16 日, 一家尖端的分布式数据库技术公司 PingCAP Inc. 正式宣布发布 [TiDB][4] 1.0。TiDB 是一个开源的分布式混合事务/分析处理 (HTAP) 数据库,它使企业能够使用单个数据库来满足这两个负载。
|
||||
|
||||
在当前的数据库环境中,基础架构工程师通常要使用一个数据库进行在线事务处理(OLTP),另一个用于在线分析处理(OLAP)。TiDB 旨在通过构建一个基于实时事务数据的实时业务分析的 HTAP 数据库来打破这种分离。有了 TiDB,工程师现在可以花更少的时间来管理多个数据库解决方案,并有更多的时间为他们的公司提供业务价值。TiDB 的一个金融证券公司的用户正在利用这项技术为财富管理和用户角色的应用提供支持。借助 TiDB,该公司可以轻松处理 web 量级的计费记录,并进行关键任务时间敏感的数据分析。
|
||||
|
||||
PingCAP 联合创始人兼 CEO 刘奇(Max Liu)说:
|
||||
|
||||
> “两年半前,Edward、Dylan 和我开始这个旅程,为长期困扰基础设施软件业的老问题建立一个新的数据库。今天,我们很自豪地宣布,这个数据库 TiDB 可以面向生产环境了。亚伯拉罕·林肯曾经说过,‘预测未来的最好办法就是创造’,我们在 771 天前预测的未来,现在我们已经创造了,这不仅是我们团队的每一个成员,也是我们的开源社区的每个贡献者、用户和合作伙伴的努力工作和奉献。今天,我们庆祝和感谢开源精神的力量。明天,我们将继续创造我们相信的未来。”
|
||||
|
||||
TiDB 已经在亚太地区 30 多家公司投入生产环境,其中包括 [摩拜][5]、[Gaea][6] 和 [YOUZU][7] 等快速增长的互联网公司。使用案例涵盖从在线市场和游戏到金融科技、媒体和旅游的多个行业。
|
||||
|
||||
### TiDB 功能
|
||||
|
||||
**水平可扩展性**
|
||||
|
||||
TiDB 随着你的业务发展而增长。你可以通过添加更多机器来增加存储和计算能力。
|
||||
|
||||
**兼容 MySQL 协议**
|
||||
|
||||
像用 MySQL 一样使用 TiDB。你可以用 TiDB 替换 MySQL 来增强你的应用,且在大多数情况下不用更改一行代码,也几乎没有迁移成本。
|
||||
|
||||
**自动故障切换和高可用性**
|
||||
|
||||
你的数据和程序始终处于在线状态。TiDB 自动处理故障并保护你的应用免受整个数据中心的机器故障甚至停机。
|
||||
|
||||
**一致的分布式事务**
|
||||
|
||||
TiDB 类似于单机关系型数据库系统(RDBMS)。你可以启动跨多台机器的事务,而不用担心一致性。TiDB 使你的应用程序代码简单而强大。
|
||||
|
||||
**在线 DDL**
|
||||
|
||||
根据你的要求更改 TiDB 模式。你可以添加新的列和索引,而不会停止或影响你正在进行的操作。
|
||||
|
||||
[现在尝试TiDB!][8]
|
||||
|
||||
### 使用案例
|
||||
|
||||
- [yuanfudao.com 中 TiDB 如何处理快速的数据增长和复杂查询] [9]
|
||||
- [从 MySQL 迁移到 TiDB 以每天处理数千万行数据] [10]
|
||||
|
||||
### 更多信息:
|
||||
|
||||
TiDB 内部:
|
||||
|
||||
* [数据存储][1]
|
||||
* [计算][2]
|
||||
* [调度][3]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://pingcap.github.io/blog/2017/10/17/announcement/
|
||||
|
||||
作者:[PingCAP][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://pingcap.github.io/blog/
|
||||
[1]:https://pingcap.github.io/blog/2017/07/11/tidbinternal1/
|
||||
[2]:https://pingcap.github.io/blog/2017/07/11/tidbinternal2/
|
||||
[3]:https://pingcap.github.io/blog/2017/07/20/tidbinternal3/
|
||||
[4]:https://github.com/pingcap/tidb
|
||||
[5]:https://en.wikipedia.org/wiki/Mobike
|
||||
[6]:http://www.gaea.com/en/
|
||||
[7]:http://www.yoozoo.com/aboutEn
|
||||
[8]:https://pingcap.com/doc-QUICKSTART
|
||||
[9]:https://pingcap.github.io/blog/2017/08/08/tidbforyuanfudao/
|
||||
[10]:https://pingcap.github.io/blog/2017/05/22/Comparison-between-MySQL-and-TiDB-with-tens-of-millions-of-data-per-day/
|
||||
@@ -0,0 +1,43 @@
|
||||
IoT 网络安全:后备计划是什么?
|
||||
=======
|
||||
|
||||
八月份,四名美国参议员提出了一项旨在改善物联网(IoT)安全性的法案。2017 年的 “物联网网络安全改进法” 是一项小幅的立法。它没有规范物联网市场。它没有任何特别关注的行业,或强制任何公司做任何事情。甚至没有修改嵌入式软件的法律责任。无论安全多么糟糕,公司可以继续销售物联网设备。
|
||||
|
||||
法案的做法是利用政府的购买力推动市场:政府购买的任何物联网产品都必须符合最低安全标准。它要求供应商确保设备不仅可以打补丁,而且是以认证和及时的方式进行修补,没有不可更改的默认密码,并且没有已知的漏洞。这是一个你可以达到的低安全值,并且将大大提高安全性,可以说明关于物联网安全性的当前状态。(全面披露:我帮助起草了一些法案的安全性要求。)
|
||||
|
||||
该法案还将修改“计算机欺诈和滥用”和“数字千年版权”法案,以便安全研究人员研究政府购买的物联网设备的安全性。这比我们的行业需求要窄得多。但这是一个很好的第一步,这可能是对这个立法最好的事。
|
||||
|
||||
不过,这一步甚至不可能施行。我在八月份写这个专栏,毫无疑问,这个法案你在十月份或以后读的时候会没有了。如果听证会举行,它们无关紧要。该法案不会被任何委员会投票,不会在任何立法日程上。这个法案成为法律的可能性是零。这不仅仅是因为目前的政治 - 我在奥巴马政府下同样悲观。
|
||||
|
||||
但情况很严重。互联网是危险的 - 物联网不仅给了眼睛和耳朵,而且还给手脚。一旦有影响到位和字节的安全漏洞、利用和攻击现在将会影响到其血肉。
|
||||
|
||||
正如我们在过去一个世纪一再学到的那样,市场是改善产品和服务安全的可怕机制。汽车、食品、餐厅、飞机、火灾和金融仪器安全都是如此。原因很复杂,但基本上卖家不会在安全方面进行竞争,因为买方无法根据安全考虑有效区分产品。市场使用的竞相降低门槛的机制价格降到最低的同时也将质量降至最低。没有政府干预,物联网仍然会很不安全。
|
||||
|
||||
美国政府对干预没有兴趣,所以我们不会看到严肃的安全和保障法规、新的联邦机构或更好的责任法。我们可能在欧盟有更好的机会。根据“通用数据保护条例”在数据隐私的规定,欧盟可能会在 5 年内通过类似的安全法。没有其他国家有足够的市场份额来做改变。
|
||||
|
||||
有时我们可以选择不使用物联网,但是这个选择变得越来越少见了。去年,我试着不连接网络来购买新车但是失败了。再过几年, 就几乎不可能不连接到物联网。我们最大的安全风险将不会来自我们与之有市场关系的设备,而是来自其他人的汽车、照相机、路由器、无人机等等。
|
||||
|
||||
我们可以尝试为理想买单,并要求更多的安全性,但企业不会在物联网安全方面进行竞争 - 而且我们的安全专家不是一个可以产生影响的足够大的市场力量。
|
||||
|
||||
我们需要一个后备计划,虽然我不知道是什么。如果你有任何想法请评论。
|
||||
|
||||
这篇文章以前出现在 9/10 月的 《IEEE 安全与隐私》上。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
自从 2004 年以来,我一直在博客上写关于安全的文章,以及从 1998 年以来我的每月订阅中也有。我写书、文章和学术论文。目前我是 IBM Resilient 的首席技术官,哈佛伯克曼中心的研究员,EFF 的董事会成员。
|
||||
|
||||
------------------
|
||||
|
||||
|
||||
via: https://www.schneier.com/blog/archives/2017/10/iot_cybersecuri.html
|
||||
|
||||
作者:[Bruce Schneier][a]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.schneier.com/blog/about/
|
||||
157
published/201710/20171019 How to run DOS programs in Linux.md
Normal file
157
published/201710/20171019 How to run DOS programs in Linux.md
Normal file
@@ -0,0 +1,157 @@
|
||||
怎么在 Linux 中运行 DOS 程序
|
||||
============================================================
|
||||
|
||||
> QEMU 和 FreeDOS 使得很容易在 Linux 中运行老的 DOS 程序
|
||||
|
||||

|
||||
|
||||
Image by : opensource.com
|
||||
|
||||
传统的 DOS 操作系统支持的许多非常优秀的应用程序: 文字处理,电子表格,游戏和其它的程序。但是一个应用程序太老了,并不意味着它没用了。
|
||||
|
||||
如今有很多理由去运行一个旧的 DOS 应用程序。或许是从一个遗留的业务应用程序中提取一个报告,或者是想玩一个经典的 DOS 游戏,或者只是因为你对“传统计算机”很好奇。你不需要去双引导你的系统去运行 DOS 程序。取而代之的是,你可以在 Linux 中在一个 PC 仿真程序和 [FreeDOS][18] 的帮助下去正确地运行它们。
|
||||
|
||||
FreeDOS 是一个完整的、免费的、DOS 兼容的操作系统,你可以用它来玩经典的游戏、运行旧式业务软件,或者开发嵌入式系统。任何工作在 MS-DOS 中的程序也可以运行在 FreeDOS 中。
|
||||
|
||||
在那些“过去的时光”里,你安装的 DOS 是作为一台计算机上的独占操作系统。 而现今,它可以很容易地安装到 Linux 上运行的一台虚拟机中。 [QEMU][19] (<ruby>快速仿真程序<rt>Quick EMUlator</rt></ruby>的缩写) 是一个开源的虚拟机软件,它可以在 Linux 中以一个“<ruby>访客<rt>guest</rt></ruby>”操作系统来运行 DOS。许多流行的 Linux 系统都默认包含了 QEMU 。
|
||||
|
||||
通过以下四步,很容易地在 Linux 下通过使用 QEMU 和 FreeDOS 去运行一个老的 DOS 程序。
|
||||
|
||||
### 第 1 步:设置一个虚拟磁盘
|
||||
|
||||
你需要一个地方来在 QEMU 中安装 FreeDOS,为此你需要一个虚拟的 C: 驱动器。在 DOS 中,字母`A:` 和 `B:` 是分配给第一和第二个软盘驱动器的,而 `C:` 是第一个硬盘驱动器。其它介质,包括其它硬盘驱动器和 CD-ROM 驱动器,依次分配 `D:`、`E:` 等等。
|
||||
|
||||
在 QEMU 中,虚拟磁盘是一个镜像文件。要初始化一个用做虚拟 `C: ` 驱动器的文件,使用 `qemu-img` 命令。要创建一个大约 200 MB 的镜像文件,可以这样输入:
|
||||
|
||||
```
|
||||
qemu-img create dos.img 200M
|
||||
```
|
||||
|
||||
与现代计算机相比, 200MB 看起来非常小,但是早在 1990 年代, 200MB 是非常大的。它足够安装和运行 DOS。
|
||||
|
||||
### 第 2 步: QEMU 选项
|
||||
|
||||
与 PC 仿真系统 VMware 或 VirtualBox 不同,你需要通过 QEMU 命令去增加每个虚拟机的组件来 “构建” 你的虚拟系统 。虽然,这可能看起来很费力,但它实际并不困难。这些是我们在 QEMU 中用于去引导 FreeDOS 的参数:
|
||||
|
||||
| | |
|
||||
|:-- |:--|
|
||||
| `qemu-system-i386` | QEMU 可以仿真几种不同的系统,但是要引导到 DOS,我们需要有一个 Intel 兼容的 CPU。 为此,使用 i386 命令启动 QEMU。 |
|
||||
| `-m 16` | 我喜欢定义一个使用 16MB 内存的虚拟机。它看起来很小,但是 DOS 工作不需要很多的内存。在 DOS 时代,计算机使用 16MB 或者 8MB 内存是非常普遍的。 |
|
||||
| `-k en-us` | 从技术上说,这个 `-k` 选项是不需要的,因为 QEMU 会设置虚拟键盘去匹配你的真实键盘(在我的例子中, 它是标准的 US 布局的英语键盘)。但是我还是喜欢去指定它。 |
|
||||
| `-rtc base=localtime` | 每个传统的 PC 设备有一个实时时钟 (RTC) 以便于系统可以保持跟踪时间。我发现它是设置虚拟 RTC 匹配你的本地时间的最简单的方法。 |
|
||||
| `-soundhw sb16,adlib,pcspk` | 如果你需要声音,尤其是为了玩游戏时,我更喜欢定义 QEMU 支持 SoundBlaster 16 声音硬件和 AdLib 音乐。SoundBlaster 16 和 AdLib 是在 DOS 时代非常常见的声音硬件。一些老的程序也许使用 PC 喇叭发声; QEMU 也可以仿真这个。 |
|
||||
| `-device cirrus-vga` | 要使用图像,我喜欢去仿真一个简单的 VGA 视频卡。Cirrus VGA 卡是那时比较常见的图形卡, QEMU 可以仿真它。 |
|
||||
| `-display gtk` | 对于虚拟显示,我设置 QEMU 去使用 GTK toolkit,它可以将虚拟系统放到它自己的窗口内,并且提供一个简单的菜单去控制虚拟机。 |
|
||||
| `-boot order=` | 你可以告诉 QEMU 从多个引导源来引导虚拟机。从软盘驱动器引导(在 DOS 机器中一般情况下是 `A:` )指定 `order=a`。 从第一个硬盘驱动器引导(一般称为 `C:`) 使用 `order=c`。 或者去从一个 CD-ROM 驱动器(在 DOS 中经常分配为 `D:` ) 使用 `order=d`。 你可以使用组合字母去指定一个特定的引导顺序, 比如 `order=dc` 去第一个使用 CD-ROM 驱动器,如果 CD-ROM 驱动器中没有引导介质,然后使用硬盘驱动器。 |
|
||||
|
||||
### 第 3 步: 引导和安装 FreeDOS
|
||||
|
||||
现在 QEMU 已经设置好运行虚拟机,我们需要一个 DOS 系统来在那台虚拟机中安装和引导。 FreeDOS 做这个很容易。它的最新版本是 FreeDOS 1.2, 发行于 2016 年 12 月。
|
||||
|
||||
从 [FreeDOS 网站][20]上下载 FreeDOS 1.2 的发行版。 FreeDOS 1.2 CD-ROM “standard” 安装器 (`FD12CD.iso`) 可以很好地在 QEMU 上运行,因此,我推荐使用这个版本。
|
||||
|
||||
安装 FreeDOS 很简单。首先,告诉 QEMU 使用 CD-ROM 镜像并从其引导。 记住,第一个硬盘驱动器是 `C:` 驱动器,因此, CD-ROM 将以 `D:` 驱动器出现。
|
||||
|
||||
```
|
||||
qemu-system-i386 -m 16 -k en-us -rtc base=localtime -soundhw sb16,adlib -device cirrus-vga -display gtk -hda dos.img -cdrom FD12CD.iso -boot order=d
|
||||
```
|
||||
|
||||
正如下面的提示,你将在几分钟内安装完成 FreeDOS 。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
在你安装完成之后,关闭窗口退出 QEMU。
|
||||
|
||||
### 第 4 步:安装并运行你的 DOS 应用程序
|
||||
|
||||
一旦安装完 FreeDOS,你可以在 QEMU 中运行各种 DOS 应用程序。你可以在线上通过各种档案文件或其它[网站][21]找到老的 DOS 程序。
|
||||
|
||||
QEMU 提供了一个在 Linux 上访问本地文件的简单方法。比如说,想去用 QEMU 共享 `dosfiles/` 文件夹。 通过使用 `-drive` 选项,简单地告诉 QEMU 去使用这个文件夹作为虚拟的 FAT 驱动器。 QEMU 将像一个硬盘驱动器一样访问这个文件夹。
|
||||
|
||||
```
|
||||
-drive file=fat:rw:dosfiles/
|
||||
```
|
||||
|
||||
现在,你可以使用合适的选项去启动 QEMU,加上一个外部的虚拟 FAT 驱动器:
|
||||
|
||||
```
|
||||
qemu-system-i386 -m 16 -k en-us -rtc base=localtime -soundhw sb16,adlib -device cirrus-vga -display gtk -hda dos.img -drive file=fat:rw:dosfiles/ -boot order=c
|
||||
```
|
||||
|
||||
一旦你引导进入 FreeDOS,你保存在 `D:` 驱动器中的任何文件将被保存到 Linux 上的 `dosfiles/` 文件夹中。可以从 Linux 上很容易地直接去读取该文件;然而,必须注意的是,启动 QEMU 后,不能从 Linux 中去改变 `dosfiles/` 这个文件夹。 当你启动 QEMU 时,QEMU 一次性构建一个虚拟的 FAT 表,如果你在启动 QEMU 之后,在 `dosfiles/` 文件夹中增加或删除文件,仿真程序可能会很困惑。
|
||||
|
||||
我使用 QEMU 像这样运行一些我收藏的 DOS 程序, 比如 As-Easy-As 电子表格程序。这是一个在上世纪八九十年代非常流行的电子表格程序,它和现在的 Microsoft Excel 和 LibreOffice Calc 或和以前更昂贵的 Lotus 1-2-3 电子表格程序完成的工作是一样的。 As-Easy-As 和 Lotus 1-2-3 都保存数据为 WKS 文件,最新版本的 Microsoft Excel 不能读取它,但是,根据兼容性, LibreOffice Calc 可以支持它。
|
||||
|
||||

|
||||
|
||||
*As-Easy-As 电子表格程序*
|
||||
|
||||
我也喜欢在 QEMU中引导 FreeDOS 去玩一些收藏的 DOS 游戏,比如原版的 Doom。这些老的 DOS 游戏玩起来仍然非常有趣, 并且它们现在在 QEMU 上运行的非常好。
|
||||
|
||||

|
||||
|
||||
*Doom*
|
||||
|
||||

|
||||
|
||||
*Heretic*
|
||||
|
||||

|
||||
|
||||
*Jill of the Jungle*
|
||||
|
||||

|
||||
|
||||
*Commander Keen*
|
||||
|
||||
QEMU 和 FreeDOS 使得在 Linux 上运行老的 DOS 程序变得很容易。你一旦设置好了 QEMU 作为虚拟机仿真程序并安装了 FreeDOS,你将可以在 Linux 上运行你收藏的经典的 DOS 程序。
|
||||
|
||||
_所有图片要致谢 [FreeDOS.org][16]。_
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
作者简介:
|
||||
|
||||
Jim Hall 是一位开源软件的开发者和支持者,可能最广为人知的是他是 FreeDOS 的创始人和项目协调者。 Jim 也非常活跃于开源软件适用性领域,作为 GNOME Outreachy 适用性测试的导师,同时也作为一名兼职教授,教授一些开源软件适用性的课程,从 2016 到 2017, Jim 在 GNOME 基金会的董事会担任董事,在工作中, Jim 是本地政府部门的 CIO。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/17/10/run-dos-applications-linux
|
||||
|
||||
作者:[Jim Hall][a]
|
||||
译者:[qhwdw](https://github.com/qhwdw)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/jim-hall
|
||||
[1]:https://opensource.com/resources/what-is-linux?intcmp=70160000000h1jYAAQ&utm_source=intcallout&utm_campaign=linuxcontent
|
||||
[2]:https://opensource.com/resources/what-are-linux-containers?intcmp=70160000000h1jYAAQ&utm_source=intcallout&utm_campaign=linuxcontent
|
||||
[3]:https://developers.redhat.com/promotions/linux-cheatsheet/?intcmp=70160000000h1jYAAQ&utm_source=intcallout&utm_campaign=linuxcontent
|
||||
[4]:https://developers.redhat.com/cheat-sheet/advanced-linux-commands-cheatsheet?intcmp=70160000000h1jYAAQ&utm_source=intcallout&utm_campaign=linuxcontent
|
||||
[5]:https://opensource.com/tags/linux?intcmp=70160000000h1jYAAQ&utm_source=intcallout&utm_campaign=linuxcontent
|
||||
[6]:https://opensource.com/file/374821
|
||||
[7]:https://opensource.com/file/374771
|
||||
[8]:https://opensource.com/file/374776
|
||||
[9]:https://opensource.com/file/374781
|
||||
[10]:https://opensource.com/file/374761
|
||||
[11]:https://opensource.com/file/374786
|
||||
[12]:https://opensource.com/file/374791
|
||||
[13]:https://opensource.com/file/374796
|
||||
[14]:https://opensource.com/file/374801
|
||||
[15]:https://opensource.com/article/17/10/run-dos-applications-linux?rate=STdDX4LLLyyllTxAOD-CdfSwrZQ9D3FNqJTpMGE7v_8
|
||||
[16]:http://www.freedos.org/
|
||||
[17]:https://opensource.com/user/126046/feed
|
||||
[18]:http://www.freedos.org/
|
||||
[19]:https://www.qemu.org/
|
||||
[20]:http://www.freedos.org/
|
||||
[21]:http://www.freedos.org/links/
|
||||
[22]:https://opensource.com/users/jim-hall
|
||||
[23]:https://opensource.com/users/jim-hall
|
||||
[24]:https://opensource.com/article/17/10/run-dos-applications-linux#comments
|
||||
@@ -0,0 +1,75 @@
|
||||
为什么要切换到 Linux 系统?我该怎么做?
|
||||
============================================================
|
||||
|
||||
> 是时候做出改变了。
|
||||
|
||||

|
||||
|
||||
*让 Ubuntu,一个简单易用的 Linux 版本,来掌管你的电脑。*
|
||||
|
||||
当你在选购电脑的时候,你可能会在 [Windows][1] 和 [macOS][2] 之间犹豫,但是可能基本不会想到 Linux。尽管如此,这个名气没那么大的操作系统仍然拥有庞大而忠诚的粉丝。因为它相对于它的竞争者,有很大的优势。
|
||||
|
||||
不管你是完全不了解 Linux,或是已经尝试过一两次,我们希望你考虑一下在你的下一台笔记本或台式机上运行 Linux,或者可以和现存系统做个双启动。请继续阅读下去,看是不是时候该切换了。
|
||||
|
||||
### 什么是 Linux?
|
||||
|
||||
如果你已经非常熟悉 Linux,可以跳过这个部分。对于不熟悉的其他人,Linux 是一个免费的开源操作系统,所有人都可以去探索它的代码。技术上来说,术语“Linux”说的只是内核,或者核心代码。不过,人们一般用这个名字统称整个操作系统,包括界面和集成的应用。
|
||||
|
||||
因为所有人都可以修改它,Linux 有非常自由的可定制性,这鼓舞了很多程序员制作并发布了自己的系统——常称为<ruby>发行版<rt>distro</rt></ruby>。这些不同口味的系统,每一个都有自己特色的软件和界面。一些比较有名的发行版,模仿了熟悉的 Windows 或 macOS 操作系统,比如 [Ubuntu][3]、 [Linux Mint][4] 和 [Zorin OS][5]。当你准备选择一个发行版时,可以去它们官网看一下,试试看是不是适合自己。
|
||||
|
||||
为了制作和维护这些 Linux 发行版,大量的开发者无偿地贡献了自己的时间。有时候,利润主导的公司为了拓展自己的软件销售领域,也会主导开发带有独特特性的 Linux 版本。比如 Android(它虽然不能当作一个完整的 Linux 操作系统)就是以基于 Linux 内核的,这也是为什么它有很多[不同变种][6]的原因。另外,很多服务器和数据中心也运行了 Linux,所以很有可能这个操作系统托管着你正在看的网页。
|
||||
|
||||
### 有什么好处?
|
||||
|
||||
首先,Linux 是免费而且开源的,意味着你可以将它安装到你现有的电脑或笔记本上,或者你自己组装的机器,而不用支付任何费用。系统会自带一些常用软件,包括网页浏览器、媒体播放器、[图像编辑器][7]和[办公软件][8],所以你也不用为了查看图片或处理文档再支付其他额外费用。而且,以后还可以免费升级。
|
||||
|
||||
Linux 比其他系统能更好的防御恶意软件,强大到你都不需要运行杀毒软件。开发者们在最早构建时就考虑了安全性,比如说,操作系统只运行可信的软件。而且,很少有恶意软件针对这个系统,对于黑客来说,这样做没有价值。Linux 也并不是完全没有任何漏洞,只不过对于一般只运行已验证软件的家庭用户来说,并不用太担心安全性。
|
||||
|
||||
这个操作系统对硬件资源的要求比起数据臃肿的 Windows 或 macOS 来说也更少。一些发行版不像它们名气更大的表兄弟,默认集成了更少的组件,开发者特别开发了一些,比如 [Puppy Linux][9] 和 [Linux Lite][10],让系统尽可能地轻量。这让 Linux 非常适合那些家里有很老的电脑的人。如果你的远古笔记本正在原装操作系统的重压下喘息,试试装一个 Linux,应该会快很多。如果不愿意的话,你也不用抛弃旧系统,我们会在后面的部分里解释怎么做。
|
||||
|
||||
尽管你可能会需要一点时间来适应新系统,不过不用太久,你就会发现 Linux 界面很容易使用。任何年龄和任何技术水平的人都可以掌握这个软件。而且在线的 Linux 社区提供了大量的帮助和支持。说到社区,下载 Linux 也是对开源软件运动的支持:这些开发者一起工作,并不收取任何费用,为全球用户开发更优秀的软件。
|
||||
|
||||
### 我该从哪儿开始?
|
||||
|
||||
Linux 据说只有专家才能安装。不过比起前几年,现在安装并运行操作系统已经非常简单了。
|
||||
|
||||
首先,打开你喜欢的发行版的网站,按照上面的安装指南操作。一般会需要烧录一张 DVD 或者制作一个带有必要程序的 U 盘,然后重启你的电脑,执行这段程序。实际上,这个操作系统的一个好处是,你可以将它直接安装在可插拔的 U 盘上,我们有一个[如何把电脑装在 U 盘里][11]的完整指南。
|
||||
|
||||
如果你想在不影响原来旧系统的情况下运行 Linux,你可以选择从 DVD 或 U 盘或者电脑硬盘的某个分区(分成不同的区来独立运行不同的操作系统)单独启动。有些 Linux 发行版在安装过程中会帮你自动处理磁盘分区。或者你可以用[磁盘管理器][12] (Windows)或者[磁盘工具][13] (macOS)自己调整分区。
|
||||
|
||||
这些安装说明可能看上去很模糊,但是不要担心:每个发行版都会提供详细的安装指引,虽然大多数情况下过程都差不多。比如,如果你想安装 Ubuntu(最流行的家用 Linux 发行版中的一个),可以[参考这里的指引][14]。(在安装之前,你也可以[尝试运行一下][15])你需要下载最新的版本,烧录到 DVD 或是 U 盘里,然后再用光盘或 U 盘引导开机,然后跟随安装向导里的指引操作。安装完成时提示安装额外软件时,Ubuntu 会引导你打开合适的工具。
|
||||
|
||||
如果你要在一台全新的电脑上安装 Linux,那没什么需要特别留意的。不过如果你要保留旧系统的情况下安装新系统,我们建议你首先[备份自己的数据][16]。在安装过程中,也要注意选择双启动选项,避免擦除现有的系统和文件。你选好的发行版的介绍里会有更详细的说明:你可以在[这里][17]查看 Zorin OS 的完整介绍,[这里][18]有 Linux Mint的,其他发行版的介绍在他们各自的网站上也都会有。
|
||||
|
||||
就这些了!那么,你准备好试试 Linux 了吗?
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.popsci.com/switch-to-linux-operating-system
|
||||
|
||||
作者:[David Nield][a]
|
||||
译者:[zpl1025](https://github.com/zpl1025)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://www.popsci.com/authors/david-nield
|
||||
[1]:https://www.popsci.com/windows-tweaks-improve-performance
|
||||
[2]:https://www.popsci.com/macos-tweaks-improve-performance
|
||||
[3]:https://www.ubuntu.com/
|
||||
[4]:https://linuxmint.com/
|
||||
[5]:https://zorinos.com/
|
||||
[6]:https://lineageos.org/
|
||||
[7]:https://www.gimp.org/
|
||||
[8]:https://www.libreoffice.org/
|
||||
[9]:http://puppylinux.org/main/Overview%20and%20Getting%20Started.htm
|
||||
[10]:https://www.linuxliteos.com/
|
||||
[11]:https://www.popsci.com/portable-computer-usb-stick
|
||||
[12]:https://www.disk-partition.com/windows-10/windows-10-disk-management-0528.html
|
||||
[13]:https://support.apple.com/kb/PH22240?locale=en_US
|
||||
[14]:https://tutorials.ubuntu.com/tutorial/tutorial-install-ubuntu-desktop?backURL=%2F#0
|
||||
[15]:https://tutorials.ubuntu.com/tutorial/try-ubuntu-before-you-install?backURL=%2F#0
|
||||
[16]:https://www.popsci.com/back-up-and-protect-your-data
|
||||
[17]:https://zorinos.com/help/install-zorin-os/
|
||||
[18]:https://linuxmint.com/documentation.php
|
||||
[19]:https://www.popsci.com/authors/david-nield
|
||||
Reference in New Issue
Block a user