助你掌握 C++ 编程的九本开源书籍

这些书可以在不学网下载pdf电子书 noxue.com/p/760336371396903

书籍是非常主观和私人的财产，编程书籍也不例外。但是不管 C++ 编程书籍的风格、关注点或者节奏如何，好书总可以带领读者走过一段引人入胜的旅程，揭示编程语言的能力，还能向读者描绘如何使用编程语言来实现各种事物。

![](/data/attachment/album/201703/01/104242jjujlty7uxzayybx.jpg)

分享是一种美德，我精心挑选了九本值得一读的优质 C++ 书籍，这些书籍均基于开源协议发布。在这之前，我会给出一份 C++ 的简短介绍。

C++ 是由 Bjarne Stroustrup 设计，初次发布于 1983 年。它是一种静态类型、格式自由、多重范式、可移植、编译式的通用编程语言。它被认为是中级语言，同时包含有高级与初级编程语言的特性。C++ 设计用于实现系统级与应用的编程，它拓展了 C 编程语言，因此其名字中的使用了自增运算符 ++。

C++ 仍是一种流行的编程语言。例如，它被广泛应用于嵌入式系统、银行以及通讯业。它作为 C 的超集保留了 C 标志性的简洁与高效，同时提供强类型检查、多重继承、数据抽象、异常处理操作符重载、泛型编程，以及面向对象编程等功能。C++ 不仅影响了 C 语言的发展，同时也影响到了包括 C#、Java 在内的许多其他编程语言。

### 《<ruby> Boost C++ 类库 <rp>  （ </rp> <rt>  The Boost C++ Libraries </rt> <rp>  ） </rp></ruby>》

![The Boost C++ Libraries](/data/attachment/album/201703/01/104310u30u0unuts0w0q24.jpg)

作者 Boris Schäling (570页)

《<ruby>   Boost C++ 类库  <rp>  （ </rp> <rt>  The Boost C++ Libraries </rt> <rp>  ） </rp></ruby>》被认为在 C++ 世界中极其重要并有深远影响。书中这些可移植的库提供对多种任务和结构体的支持，包括多线程、容器、字符串和文本处理、迭代器、线性代数、伪随机数产生、元程序设计模板、并发程序设计、数据结构、图像处理、正则表达式和单元测试。Boost 可以在几乎所有现代操作系统上工作，包括 Linux 和 Windows 及其衍生，并且支持绝大多数现代编译器。

这本书介绍了 72 个 Boost 库，提供了广泛并且实用的功能。它们能够帮助程序员更轻松的管理内存和处理字符串。这些库提供多种容器以及其它数据结构来拓展标准库。使用这些库可以轻松实现平台无关的网络应用程序。

本书是一颗值得添加到各类收藏中的明珠。430 份源代码例子生动形象地阐释了这些库的功能。

本书前面的章节审视了内存管理、字符串处理、容器、数据结构、算法、通信、文件与流以及时间。后续章节进一步探索了函数式编程、并行编程和泛型编程。以对语言拓展、错误与数字处理、应用程序库、设计模式以及其他库的大部分内容。

本书采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-nd/4.0/)进行许可。如果你喜欢实体书，可以在亚马逊上购买纸质书，也可选择如 kindle、E-book 和 PD F格式的电子书。

### 《<ruby> C++ 注释 <rp>  （ </rp> <rt>  C++ Annotations </rt> <rp>  ） </rp></ruby>》

![C++ Annotations](/data/attachment/album/201703/01/103613dy2iybyebdnjwciw.jpg)

作者 Frank B. Brokken (1029页)

《<ruby>   C++ 注释  <rp>  （ </rp> <rt>  C++ Annotations </rt> <rp>  ） </rp></ruby>》提供了关于 C++ 编程语言的一份全面的教程。它可以被当做一门 C++ 课程的教材。本书面向已掌握 C 语言或其他类 C 语法知识的使用者。

本书主要章节有：

* 命名空间
* 字符串——C 提供的是最基础的字符串支持
* I/O 流库——提供了一个基于类概念的输入/输出库
* 类——C 提供了两种结构化不同类型数据的方法。C 语言的结构体包含多种类型的数据成员，而 C 语言的<ruby> 共用体 <rp>  （ </rp> <rt>  union </rt> <rp>  ） </rp></ruby>同样可以定义不同类型的数据成员。本章介绍的类，也是一种结构体但是它的内容对外部世界来说是默认不可访问的。
* 静态数据和函数
* 内存管理——审视 C++ 中能够操作内存分配的操作符
* 异常——允许 C++ 程序执行受控的非本地返回命令，避免了使用 longjmp（非局部跳转）和 setjmp（激活非局部跳转）函数的缺陷。
* 操作符重载——介绍通用的操作符重载
* 抽象容器
* 继承——派生的另外一个特性。 本章演示基类指针可能被用来指向派生类的对象。
* 多态——继承的一种特殊形态
* 友元机制——介绍 friend 关键词以及它的使用原则
* 成员指针——定义成员指针及其用法，静态成员指针，指针长度
* 嵌套类——当嵌套类与其外围类有概念上的紧密联系时会被使用
* 标准模板库（STL）——包含容器、通用算法、迭代器、函数对象、分配器、适配器和数据结构的通用库。这些算法使用的数据结构都是抽象意义的，因此算法实际上可以使用任何数据类型。
* 泛型算法——涵盖标准模板库中的泛型算法
* 函数模板——探索模板独特的句法。介绍模板类型参数与模板非类型参数和函数模板等概念并且提供模板的多个案例。
* 类模板——讨论构建与使用类模板
* 进阶模板用法——一份简短的模板元编程主要特性与模板的微妙关系的介绍

本书有HTML、PDF、PostScript 和 txt 版本。它可以自由分发，基于 GNU GPL 协议发布。

### 《<ruby> 通过 C++ 和 Qt4 了解设计模式 <rp>  （ </rp> <rt>  An Introduction to Design Patterns in C++ with Qt 4 </rt> <rp>  ） </rp></ruby>》

![Introduction to Design Patterns in C++ with Qt 4, An](/data/attachment/album/201703/01/104311auu95wch4f0h9244.jpg)

作者 Alan Ezust, Paul Ezust (656页)

《<ruby>   通过 C++ 和 Qt4 了解设计模式  <rp>  （ </rp> <rt>  An Introduction to Design Patterns in C++ with Qt 4 </rt> <rp>  ） </rp></ruby>》从介绍 C++ 基础知识、面向对象概念、UML（统一建模语言）和核心 Qt 类开始。进而转向更高级的编程概念如 Qt modules 和设计模式。最后严密地印证了重要的 C++ 特性。其中涵盖了非常优秀的对函数、继承和多态的讲解。

本书不需要具备任何 C 或者 C++ 编程经验前提，并且被设计为普适用途。它也为教学工作者准备了 Qt 案例、练习、答案以及课程 PPT。

本书是 Bruce Perens 开源系列中的一部分。所有在此系列中的书籍都是基于 Open Publication License V1.0 及其后版本发布的。

### 《<ruby> 像计算机科学家一样思考：C++ <rp>  （ </rp> <rt>  How to Think Like a Computer Scientist: C++ </rt> <rp>  ） </rp></ruby>》

![How to Think Like a Computer Scientist: C++](/data/attachment/album/201703/01/104311javufdza0zr2da2r.jpg)

作者 Allen B. Downey (191页)

《<ruby>   像计算机科学家一样思考：C++  <rp>  （ </rp> <rt>  How to Think Like a Computer Scientist: C++ </rt> <rp>  ） </rp></ruby>》是使用 C++ 编程语言进行软件设计的一本简洁友好的介绍性书籍。本书的目的是通过讲解最基础的概念并且以读者容易接受的节奏逐步增加新内容来把没有编程经验的读者培养成为未来的开发者。

本书提供的有价值的内容包括：

* 变量、表达式和语句
* 函数
* 条件语句与递归
* 丰富的函数
* 迭代
* 字符串
* 向量
* 成员函数
* 对象的向量
* 向量的对象
* 类与不变量
* 文件输入输出和 apmatrixes

本书是一本在[知识共享署名-非商业性使用-3.0 未本地化版本](https://creativecommons.org/licenses/by-nc/3.0/)协议下发布的免费书籍。

### 《<ruby> C++ Qt3 图形界面编程 <rp>  （ </rp> <rt>  C++ GUI Programming with Qt 3 </rt> <rp>  ） </rp></ruby>》

![C++ GUI Programming with Qt 3](/data/attachment/album/201703/01/104312cuvhg0lf077ick03.jpg)

作者 Jasmin Blanchette, Mark Summerfield (464 页)

最新发布的 Qt 稳定版本是 5.8，而《<ruby>   C++ Qt3 图形界面编程  <rp>  （ </rp> <rt>  C++ GUI Programming with Qt 3 </rt> <rp>  ） </rp></ruby>》指导读者如何使用 Qt3 编写 GUI 程序，Qt3 最近一次更新是 2004 年，但是本书仍然有大部分内容对 Qt4 和 Qt5 有效。

本书不是一本面向初学者的书，需要读者对 C++ 有基本的理解。

本书向读者介绍了使用 Qt 进行 GUI 应用编程所有的概念和需要的练习。本书不仅详尽的讲述了核心内容，而且也提供了一些特别的和高阶的学习材料。

本书是 Bruce Perens 开源系列中的一部分。所有在此系列中的书籍都是基于 Open Publication License V1.0及之后版本发布的。

### 《<ruby> 开放数据结构（C++ 版） <rp>  （ </rp> <rt>  Open Data Structures (in C++) </rt> <rp>  ） </rp></ruby>》

![Open Data Structures (in C++)](/data/attachment/album/201703/01/104314yc7mk6zvk774vrzf.jpg)

作者 Pat Morin (336页)

《<ruby>   开放数据结构（C++ 版）  <rp>  （ </rp> <rt>  Open Data Structures (in C++) </rt> <rp>  ） </rp></ruby>》教导读者设计和分析基础数据结构以及如何使用 C++ 实现。 它涵盖了对如下数据结构的分析和实现：序列（列表）、队列、优先队列、无序字典、有序字典以及图。作者的意图在于向大学计算机科学的学生提供一条学习数据结构的免费的途径，但是并不打算成为介绍 C++ 编程语言或者 C++ 标准模板库的一本书。不过它可以帮助程序员理解标准模板库的数据结构是如何实现的以及这种实现为何如此高效。

章节内容覆盖了基于数组的列表、链表、分级链表、哈希表、二叉树（又包含了随机二叉搜索树、替罪羊树、红黑树）。之后的章节还包括堆、排序算法（比较、计数和基数排序）、图、整数的数据结构和外部存储器搜索。

本书基于[知识共享署名](http://creativecommons.org/licenses/by/4.0/) 协议发布。免费的 HTML、PDF 均已发布，本书的 LaTeX 版本，Java/C++/Python 源代码都可以从 GitHub 下载。也有纸质书版本可供购买。本书已被翻译为斯罗维尼亚语和土耳其语。

### 《<ruby> 使用 wxWidgets 进行跨平台 GUI 编程 <rp>  （ </rp> <rt>  Cross-Platform GUI Programming with wxWidgets </rt> <rp>  ） </rp></ruby>》

![Cross-Platform GUI Programming with wxWidgets](/data/attachment/album/201703/01/104314qej5155nliwze1lu.jpg)

作者 Julian Smart，Kevin Hock和Stefan CsomorBrifll (744 页)

wxWidgets 是一个流行的 C++ 库，可供开发者使用单一的代码基础为 Windosw、Mac OS、Linux 和其他平台创建应用。它支持非常广泛的图形处理库。

这本书《<ruby>   使用 wxWidgets 进行跨平台 GUI 编程  <rp>  （ </rp> <rt>  Cross-Platform GUI Programming with wxWidgets </rt> <rp>  ） </rp></ruby>》从简单的介绍和起步章节开始，主要内容包括：

* 事件处理
* 窗口基础
* 画图
* 输入事件处理
* 使用 sizers 进行窗口布局
* 使用标准对话框
* 创建自定义对话框
* 图像编程
* 剪贴板与拖放动作
* 高阶窗口类
* 文件和数据流
* 内存管理，调试和错误检查
* 编写国际化应用
* 编写多线程应用
* wxSocket 编程
* 处理文档和视图
* 日臻完美你的应用

本书是 Bruce Perens 开源系列中的一部分。所有在此系列中的书籍都是基于 Open Publication License V1.0及其后版本发布的。

### 《<ruby> Rook 的 C++ 指南 <rp>  （ </rp> <rt>  The Rook’s Guide to C++ </rt> <rp>  ） </rp></ruby>》

![The Rook's Guide to C++](/data/attachment/album/201703/01/104314rod7or3dip3ear0z.jpg)

作者 Jeremy Hansen (160页)

《<ruby>   Rook 的 C++ 指南  <rp>  （ </rp> <rt>  The Rook’s Guide to C++ </rt> <rp>  ） </rp></ruby>》的章节中包含变量、常量、输入输出、数据类型和转换、条件判断语句（if、else 和 else if、switch 语句）、字符串、循环、数组、块、函数和作用域。之后的章节主要集中解决问题和定位问题，包括预处理器、高等代数、文件输入输出、指针、动态数据、类和抽象、分离编译和标准模板库。

本书大部分都是由 25 位 Norwich 大学的学生于一个黑客马拉松周末写成。当然不敢说毫无缺点，但还是一本相当不错的书。它被基于[知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本](https://creativecommons.org/licenses/by-nc-sa/3.0/deed.zh)协议发布。同时在亚马逊也有本书的纸质版可供购买。

### 《<ruby> GCC 简介 <rp>  （ </rp> <rt>  An Introduction to GCC </rt> <rp>  ） </rp></ruby>》

![An Introduction to GCC](/data/attachment/album/201703/01/104315fg0bny3rkpllq70t.png)

作者 Brian Gough (144页)

《<ruby>   GCC 简介  <rp>  （ </rp> <rt>  An Introduction to GCC </rt> <rp>  ） </rp></ruby>》介绍了 GCC，同时介绍了 GNU C 和 C++ 编译器：gcc 和 g++，均属于 GNU 编译器集合（GCC）。

本书解释了如何单独使用编译器。作者通过数年对邮件列表中发表的问题的观察，撰写本书带领读者直接接触 GCC 最重要的部分。

章节简介：

* 介绍
* 编译一个 C 程序——描述了如何使用 gcc 编译 C 程序。程序可能是由单独文件或者多个文件编译而成，也可能使用系统库和头文件。
* 编译选项——描述gcc中可以使用的编译器中常用的选项。这些选项可以修改本地库和包含文件的搜索路径，配置额外的 warning 和调试信息，预处理器宏指令和 C 方言。
* 使用预处理——描述了属于 GCC 包内的 GNU C 预处理 cpp 程序的用途。预处理将宏定义在源代码被编译前展开。预处理会在 GCC 编译 C 或者 C++ 程序时自动被调用。
* 以调试为目的编译——提供 -g 选项使目标文件和可执行文件中存储额外的调试信息。当出现错误时，这些调试信息允许从特定的机器指令回溯到源代码中相应的行。
* 优化编译——GCC 是一个优化编译器。它提供了大量的选项来使其生成的可执行文件的速度得到提升，并尽量减小可执行文件的大小。
* 编译一个 C++ 程序——描述了如何使用 GCC 来编译以 C++ 编写的程序，以及针对这门语言特定的命令行选项。
* 平台相关的选项——描述了一些通用平台上可用的选项，如 Intel 和 AMD x86 选项、x86 拓展选项、x86 64 位处理器选项、DEC Alpha 选项、SPARC 选项、POWER/powerPC 选项、复合架构支持以及浮点相关问题。
* 问题定位——GCC 提供了几项帮助和诊断选项来帮助用户在编译过程中定位问题。
* 编译器相关工具——介绍了大量能够用于和 GCC 组合使用的工具。包括：用来创建库的 GNU 压缩器 ar，以及 GNU 性能和覆盖测试工具：gprof 和 gcov。
* 编译器如何工作——描述了关于 GCC 如何将源代码转换为可执行程序的更多细节。编译是一个涉及到多种工具的多级过程，包括 GNU 编译器自身（通过 gcc 或者 g++ 前端）、GNU 汇编器 as 以及 GNU 链接器 ld。编译过程中一个完整的工具集合被称之为工具链。
* 检查编译后的文件——描述了多种有用的工具来检查可执行文件和目标文件的内容。
* 常见错误消息——描述了 gcc 和 g++ 产生的最常见的错误和警告信息。每一个案例都伴随着错误和警告产生的原因，相应的例子和可能解决问题的建议。
* 获得帮助——如果读者碰到了本书中未能涵盖的问题，还有多种参考手册更详细的描述了 GCC 和编程语言相关的主题。

本书是基于 GNU Free Documentation 协议出版的。

此外还有一些 C++ 书籍可以免费下载，但是那些并不是基于开源协议发布的，或者作者没有指定一个协议。这类书籍包括：

* 《[Thinking in C++，第二版，卷一、卷二](http://mindview.net/Books/TICPP/ThinkingInCPP2e.html)》 – 作者 Bruce Eckel (卷二作者增加了 Chuck Allison)
* 《[C++ In Action:Industrial Strength Programming](http://www.relisoft.com/book/)》– 作者 Bartosz Milewski

最后，我对刚入门的 C++ 新手的建议一定是《<ruby>   编程原理与体验（C++ 版）  <rp>  （ </rp> <rt>  Programming — Principles and Practice Using C++ </rt> <rp>  ） </rp></ruby> [(第二版)](http://stroustrup.com/Programming/)》。普遍认为这是最合适的入门书籍，由 C++ 的创始人书写，物超所值。

---

via: <https://www.ossblog.org/master-c-programming-with-open-source-books/>

作者：[Steve Emms](https://www.ossblog.org/author/steve/) 译者：[xiaow6](https://github.com/xiaow6) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


通过这九本开源好书学习 C 语言

Summary: 我仔细考察了一些我最喜爱的 C 语言书籍，并从其中选出了对我意义最大的九本。

没有code complete?

这里其实只是列出了“开源”的——也就是你不用花钱，当然盗版不在此列。

开源的书，质量都不错的！

BUILS YOUR OWN LISP,这个真的很不错，可惜我没看完就看不懂了。。。

我仔细考察了一些我最喜爱的 C 语言书籍，并从其中选出了对我意义最大的九本。

书是非常私人的东西，编程类书籍也不例外。我们都是通过阅读书籍来帮助掌握某种语言的基本知识。随后又通过阅读书籍来全面探索这门语言的灵活性。我仔细考察了一些我最喜爱的 C 语言书籍，并从其中选出了对我意义最大的九本。

![](/data/attachment/album/201703/01/120456tz267vsi56w6hd6t.jpg)

C 语言是一种通用的、面向过程的高级编程语言。也是最具流行度和影响力的编程语言之一。该语言在设计时就被设计为可以通过编译器进行编译，以此来对内存进行底层操作、高效地执行机器指令以及在运行时仅需要最小的支持。很多很多编程语言都得益于 C 语言， C 语言就像是编程界的通用语言一样。

C 语言非常易懂。它允许程序员能够以一种清晰、简单、符合逻辑的方式来组织程序。这是一种非常灵活、实用并且简洁的语言，同时它还具有非常容易读懂的语法。使用 C 语言编写的代码运行非常快。可以非常简单地操作计算机的底层设施。通过编译器的指令可以允许我们为不同体系结构的设备生成同一个版本的程序。

C 语言是自由的。因此学习 C 语言的书籍也是体现了其自由性。快来看看我挑选的开源书籍，有没有哪一本能够吸引你？好好享受它吧！

### 《 <ruby> C 语言 <rp>  （ </rp> <rt>  The C Book </rt></ruby>》

![The C Book](/data/attachment/album/201703/01/120757rc0d7zxcccecgxyr.png)

《<ruby>   C 语言  <rt>  The C Book </rt></ruby>》这本书是为那些已经具备使用现代高级面向过程编程语言的程序员设计的。这本书专注于那些 C 语言的特有之处。尤其是使用 C 语言的方式是这本书所重点关注的。作者： Mike Banahan, Declan Brady 和 Mark Doran ，350 页

包含以下章节：

* 变量及运算法则介绍 – 介绍了 C 语言的基础知识, 包括关键字和标识符，变量声明，实数类型，整型，表达式和运算法则以及常量
* 控制流及逻辑表达式 – 这一章介绍了在 C 语言中可以用于控制程序流程的几种方法，包括一些尚未介绍的语句。 控制流，逻辑表达式以及奇怪的运算符。
* 函数 – 函数的类型, 递归，传参数以及链接
* 数组和指针 – 数组, 指针, 字符处理, sizeof 以及储存空间分配, 函数指针，包含指针的表达式，数组，`&`运算符以及函数声明
* 结构体数据类型 – 结构体, 联合体, 位字段, 枚举类型, 限定符、派生类型以及初始化
* 预处理器 – 预处理器是如何工作的及其相关指令
* C 语言中的一些特定内容 – 声明， 定义，`typedef`, `const` 和 `volatile`以及序列点
* 库 – 诊断, 字符处理, 本地化, 与具体实现相关的限制, 数学函数, 非局部跳转, 信号处理, 可变参数, 输入输出, 格式化 I/O, 字符 I/O, 无格式 I/O, 随机访问函数, 通用函数, 字符串处理, 日期和时间
* C 语言编写的完整程序 – 综合以上内容, main函数的参数, 解析程序参数, 模式匹配程序以及一个难度更大的程序

作者允许读者利用此书做任何他们想做的事情，只需说明作者和版权。经 Mike Banahan 确认，本书通过知识共享协议发布。

### 《<ruby> 风格的要素—— C 语言版 <rp>  （ </rp> <rt>  C Elements of Style </rt> <rp>  ） </rp></ruby> 》

![C Elements of Style](/data/attachment/album/201703/01/120757ugl8jikm1s8e1qbs.jpg)

作者 Steve Oualline ，265页

《<ruby>   风格的要素—— C 语言版  <rp>  （ </rp> <rt>  C Elements of Style </rt> <rp>  ） </rp></ruby>》是一本十分有用的编程风格指南，指导 C 和 C++ 程序员如何去编写可读性良好的，易懂的，易于维护的程序。不论你是一个学生还是专业程序员，你都能从这本书中提供的技巧和技术中获益，构建优雅可靠的代码。

这本书意在向读者们展示如何在你的程序中融入风格良好的代码。机器仅处理代码，而人类关注于注释。良好的编程风格与这两部分都有关。

我们的最终目标是构建设计良好，编写良好的代码，这种代码对于计算机来说是高效的，同时包含了精心编写的注释用于帮助人类理解程序。这样一来，调试，维护和增强程序，都变得更加容易了。最终还提升了代码的可读性，简洁性，可靠性和可维护性。

在本书中，你可以找到有关编写注释，程序头，变量命名，语句格式化，语句细节，编写预处理器命令，组织目录和创建makefile的指南。

本书通过知识共享协议发布。

### 《<ruby> 编写你自己的 Lisp 语言 <rp>  （ </rp> <rt>  Build Your Own Lisp </rt> <rp>  ） </rp></ruby>》

![Build Your Own Lisp](/data/attachment/album/201703/01/120758nyoj1gwfo18w8j8m.jpg)

作者 Daniel Holden ，212页

在学习 C 语言的同时学习如何创造你自己的编程语言——一个最简的 Lisp 语言（仅用不到1000行代码）。

《<ruby>   编写你自己的 Lisp 语言  <rp>  （ </rp> <rt>  Build Your Own Lisp </rt> <rp>  ） </rp></ruby>》这本书是为两种人写的，一种是想要学习 C 语言的人，另一种是想要知道如何创建一门语言的人。本书并不适合作为第一本入门书籍，因为你需要一些编程经验才能读懂它的内容。

本书基于[知识共享署名-非商业性使用-3.0 未本地化版本](https://creativecommons.org/licenses/by-nc/3.0/)协议发布

纸质版可以在 Amazon 上购买。

### 《<ruby> GNU C 语言参考手册 <rp>  （ </rp> <rt>  The GNU C Reference Manual </rt> <rp>  ） </rp></ruby>》

![The GNU C Reference Manual](/data/attachment/album/201703/01/120758ftil8ik66td936ot.png)

作者 Trevis Rothwell, James Youngman ，91页

《<ruby>   GNU C 语言参考手册  <rp>  （ </rp> <rt>  The GNU C Reference Manual </rt> <rp>  ） </rp></ruby>》是一本 C 语言参考手册，意在写明 C89，C99 标准以及当前 GNU 对标准 C 语言的扩展情况。本书并不适合新手阅读。

包含以下章节：

* 词法元素——这部分内容描述了预编译之后，组成 C 语言代码的词法元素。包括 5 种：关键字，标识符，常量，操作符和分隔符
* 数据类型——研究基础的数据类型，枚举，联合体，结构体，数组，指针，不完全类型，类型修饰词，储存类型修饰词以及重命名类型
* 表达式和操作符 —— 还介绍了自增/自减，算数操作符，复共轭性，比较操作符，逻辑运算符，位移，位运算符，指针运算符，`sizeof`，类型转换等等。
* 语句 —— 标签，表达式，`if` 语句，`switch` 语句，`while` 语句，`do` 语句，`for` 语句，代码块，`null` 语句，`goto` 语句，`break` 语句，`continue` 语句，`return` 语句以及 `typedef` 语句
* 函数 —— 学习函数声明，函数定义，函数调用，参数，变长参数列表，通过指针调用函数，`main` 函数，递归函数等等。
* 程序结构和定义域 —— 从宏观上来看程序
* 一个例程 —— 一个用 C 语言编写的完整程序，包含 C 语言源文件和头文件。这个程序是一个典型“hello world”程序的扩展版，它向我们展示了在 GNU FSF 项目中 C 语言是如何工作的。

本书基于 GNU Free Documentation License Version 1.3 及其更新版本发布。

### 《<ruby> GNU C 语言编程教程 <rp>  （ </rp> <rt>  The GNU C Programming Tutorial </rt> <rp>  ） </rp></ruby>》

![The GNU C Programming Tutorial](/data/attachment/album/201703/01/120742q9ifofxxgip68106.jpg)

作者 Mark Burgess, Ron Hale-Evans ，290页

《<ruby>   GNU C 语言编程教程  <rp>  （ </rp> <rt>  The GNU C Programming Tutorial </rt> <rp>  ） </rp></ruby>》以逻辑顺序向读者介绍了 C 语言的基本内容。本书内容涵盖了 C 语言的全部主要内容的细节以及如何使用 C 语言编程，特别强调了GNU/Linux编译器以及相关软件。

一些章节主要介绍了函数，变量以及声明，作用域，表达式和操作符，参数，指针，判定，循环，数组，字符串，输入输出等等。

本书基于 GNU Free Documentation License Version 1.1 版发布。

### 《<ruby> C 语言基础 <rp>  （ </rp> <rt>  Essential C </rt> <rp>  ） </rp></ruby>》

![Essential C](/data/attachment/album/201703/01/120758bw7ojh2m0kmxwplz.png)

作者 Nick Parlante ，45页

《<ruby>   C 语言基础  <rp>  （ </rp> <rt>  Essential C </rt> <rp>  ） </rp></ruby>》这份简短的文档解释了 C 语言全部的常用特性和技术。这份文档十分简洁，因此它的目标人群是已经具有其他编程语言背景的人。

内容包括变量，整型，浮点型，类型提升，截断，操作符，控制结构（`if`,`while`,`for`），函数，数值参数，引用参数，结构体，指针，数组，预处理器以及 C 语言标准库函数。

目录：

* 简介
* 基础类型与运算符
* 控制结构
* 复杂数据类型
* 函数
* 杂项
* 高级数组与指针
* 运算符和标准库

根据作者的描述，我们得知这是一本基于开源协议发布的书籍。

### 《<ruby> Beej 的 C 语言教程 <rp>  （ </rp> <rt>  Beej’s Guide to C Programming </rt> <rp>  ） </rp></ruby>》

![Beej’s Guide to C Programming](/data/attachment/album/201703/01/120758y3dki32tjew3teru.png)

作者 Brian “Beej” Hall ，130 pages

《<ruby>   Beej 的 C 语言教程  <rt>  Beej’s Guide to C Programming </rt></ruby>》试图为那些心存困惑的读者带来一种仅能通过纯粹 C 语言编程而产生的快感。

章节：

* 构建程序的基本结构块
* 变量，表达式和，语句。变量就是一个数字的名字——一个 C 语言表达式可以包含其他的表达式以及运算符。文章还介绍了`if`，`while`，`do while`以及程序重复执行的`for`语句。

* 函数——当程序块变得很大，或是当这些程序块用来执行一些特定的工作时，我们会把它们封装到函数中。
* 变量（续篇）——探讨了变量的作用域和储存类型
* 指针——它们是数据在内存中的地址。就像一个整型的值可以是12，一个指针的值就是数据在内存中的地址
* 结构体——结构体允许你将逻辑上相关的变量分组。随后你可以将其作为一个整体来使用。
* 数组——一组相关数据的线性集合
* 字符串—— C 语言中的字符串就是内存中的一组连续的比特，包含了一组字符
* 动态内存——探讨了malloc(), free(), realloc() 和 calloc() 函数
* 更多内容——指针运算，`typedef`，枚举，结构体声明，命令行参数，多维数组，类型转换和类型提升，不完整类型，`void`指针，`NULL`指针和`static`关键字
* 标准输入输出库——用来进行文件的读写。
* 字符串操作——用来查找子串的函数，连接字符串的函数，获取字符串长度的函数，等等。
* 算数——一些可以满足你基本算数需求的函数。

本书基于[知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)协议发布。

### 《<ruby> 现代 C 语言 <rp>  （ </rp> <rt>  Modern C </rt> <rp>  ） </rp></ruby>》

![Modern C](/data/attachment/album/201703/01/120759w2622izindj4onjc.png)

作者 Jens Gustedt ，310页

《<ruby>   现代 C 语言  <rp>  （ </rp> <rt>  Modern C </rt> <rp>  ） </rp></ruby>》鼓励读者逐级探索 C 语言的特性。内容一共分为如下五层境界：

* 第一层——使读者了解最基本的 C 语言程序。了解使用 C 语言的目的和数据结构，以及如何去使用它们
* 第二层——详细的说明了大部分主要概念以及特性，包括控制结构，数据类型操作符和函数。这一级的主要目的是让读者能够对于这些运行程序必备的知识，有更深刻的理解。
* 第三层——进入 C 语言的核心部分。详细的解释了指针，让你熟悉 C 语言的内存模型，并且让你了解大多数 C 语言库的接口
* 第四层——深入理解一些特定的主题。比如性能，重入，原子性，线程及泛型编程
* 第五层——探讨了作者对这门语言未来发展趋势的理解

本书基于[知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)协议发布

### 《<ruby>   GCC 简介  <rp>  （ </rp> <rt>  An Introduction to GCC </rt> <rp>  ） </rp></ruby>》

![An Introduction to GCC](/data/attachment/album/201703/01/120759q88gagz5f1rh71sa.png)

作者 Brian Gough ，144页

《<ruby>   GCC 简介  <rp>  （ </rp> <rt>  An Introduction to GCC </rt> <rp>  ） </rp></ruby>》介绍了 GNU C 语言和 C++编译器——gcc 和 g++，它们是 GNU 编译器套装（GCC）的一部分。

本书介绍了如何使用编译器。通过多年以来对邮件列表中的问题的观察，本书带领读者直击 GCC 的重要内容。

章节介绍：

* 编译c程序——介绍了如何使用 gcc 来编译 C 语言程序。单个文件或多个文件均可以编译，也可以使用操作系统函数和头文件。
* 编译选项——介绍了 gcc 中一些常用的编译选项。这些参数控制一些功能，例如搜索库函数和头文件的路径，提供额外的编译告警和诊断。预处理宏和 C 语言方言。
* 使用预处理器——描述了cpp（GNU C preprocessor）的使用方法，cpp 是 GCC 的一部分。预处理器会在编译前将代码中的宏展开，当 GCC 处理 c 或者 c++ 代码时，预处理器会自动被调用。
* 编译带有调试信息的程序——提供了一个`-g`选项用以在中间文件和可执行文件中存储额外的调试信息。这些调试信息使得我们可以追踪错误，将机器指令中的问题定位到源代码中。
* 优化编译——GCC 是一个优化过的编译器。它提供了很多选项，可以加速编译或是减少编译后文件的体积。
* 编译C++程序——介绍了如何使用 GCC 编译 C++ 程序以及该语言可以使用的命令行选项。
* 平台相关选项——介绍了一些常见平台可以使用的选项
* 排错——GCC提供了一些帮助和诊断选项用于帮助我们定位编译过程中出现的问题。
* 编译器相关工具——介绍了 GCC 编译过程中会用到的一些有用的工具。包括用来创建库的 GNU 归档工具 ar，GNU 性能和覆盖率测试程序 gprof 和 gcov
* 编译器是如何工作的——详细介绍了 GCC 是如何把源文件编译为可执行文件的。编译是一个多阶段过程，涉及很多工具，包括 GNU 编译器本身，GNU 汇编器，以及 GNU 链接器。在编译过程中使用的各种工具，我们统称为编译链。
* 检查编译后的文件——介绍了一些用来检查可执行文件和 obj 文件内容的工具。
* 常见错误信息——介绍了 gcc 和 g++ 产生的最常见的错误和告警信息。每种情况都带有原因分析，样例以及建议的解决办法。
* 获取帮助——如果读者遇到了书中没有提到的问题，还有很多介绍GCC和语言的手册可以参考。

本书基于 GNU Free Documentation 协议发布

---

很多 C 语言书籍都可以免费下载，但是可惜的是它们并没有以开源协议来发布，或其基于何种协议并没有明确说明。以下对其进行介绍，排名不分先后：

* 《<ruby>   C编程导论  <rp>  （ </rp> <rt>  Introduction to C Programming </rt> <rp>  ） </rp></ruby>》 – 作者 Rob Miles
* 《<ruby>   新标准 C 语言  <rp>  （ </rp> <rt>  The New Standard C: An Economic and Cultural Commentary </rt> <rp>  ） </rp></ruby>》– 作者 Derek M. Jones
* 《<ruby>   使用标准 C 语言进行面向对象编程  <rp>  （ </rp> <rt>  Object-Oriented Programming with ANSI-C </rt> <rp>  ） </rp></ruby>》– 作者 Axel-Tobias Schreiner
* 《<ruby>   编写零缺陷的 C 语言代码  <rp>  （ </rp> <rt>  Writing Bug-Free C Code </rt> <rp>  ） </rp></ruby>》– 作者 Jerry Jongenius

最后，如果你想买一本权威参考书，请认准《<ruby>   C 程序设计语言  <rp>  （ </rp> <rt>  The C Programming Language </rt> <rp>  ） </rp></ruby>》。这本书非常简练，不适合新手，但是被奉为经典。


如何配置 VirtualBox 中的客户机与宿主机之间的网络连接

Summary: 当你在 VirtualBox 虚拟机软件 中安装了各种操作系统时，你可能需要实现宿主机与虚拟机之间的相互访问。

我实在是不明白，为何有那么多人放着专门修改网络参数的 ip，nmcli 命令不用，非得编辑配置文件。就不怕把不该改变的地方改到吗？用命令虽然也有该错的可能， 但几乎不会发生改 A 设置，结果把 C，D 设置也给影响到了。用命令，除了参数本身具有一定的助记功能外，还有 --help 或 man 这些来自第一方的帮助，文本文件没有注释的话，就什么提示都没有。

"sudo systemctl restart networking" 用 nmcli 更改网络设置的话， 根本不用重启网络服务，更改完成后，立即生效。

支持！我们其实也发过 ip、nmcli 的文章，不过兄弟如果有这方面的文章，或者愿意就此写点什么的话，欢迎投稿啊!

当你在 [VirtualBox 虚拟机软件](http://www.tecmint.com/install-virtualbox-on-redhat-centos-fedora/) 中安装了各种操作系统时，你可能需要实现宿主机与虚拟机之间的相互访问。

![](/data/attachment/album/201703/01/133958rqdfccfaqi5f9ktq.jpg)

在这篇文章中，我们将会以最简单明了的方式来说明如何配置客户机与 Linux 宿主机的网络，以实现两者相互访问或者让客户机连接到外网。（LCTT 译注：客户机指 Oracle VirtualBox 虚拟机软件中安装的操作系统，如本文中用到的 CentOS 7 和 Ubuntu 16.10 。宿主机就是你自己的笔记本电脑或台式机，注意这篇文章中作者的宿主机上安装的操作系统是 Linux Mint 18 ，而不是我们平时使用的 Windows 系统。）

本文测试环境： 1、宿主机操作系统—— Linux Mint 18 2、客户机操作系统—— CentOS 7 和 Ubuntu 16.10

#### 要求

1. 宿主机上安装的 VirtualBox 虚拟机 能正常使用。
2. 你得事先在 Oracle virtualBox 虚拟机软件中安装好客户机操作系统，比如 Ubuntu、Fedora、CentOS、 Linux Mint 或者其它的 Linux 系统也行。
3. 在你配置网络前，请先关闭客户机。

为了让宿主机和客户机能够互相联通，这两个机器的默认网卡 IP 必须设置在同一网段，你可以为客户机添加多达 4 块网卡。

默认网卡（网卡 1）通常用于使用 NAT 连接方式连接到宿主机进行上网。

重要提示：通常总是设置第一块网卡与宿主机通信，第二块网卡连接到外网。

### 为客户机和宿主机创建网卡

在下面的 VirtualBox 管理器界面，创建客户机和宿主机之间的通信网卡。

打开文件->首选项配置，或者使用组合键 `Ctrl + G` ：

![Virtualbox Preferences Window](/data/attachment/album/201703/01/134026pdd1ztlxlcqrrrm1.png)

*Virtualbox 首选项界面*

在下图中有两个选项，单击并选择<ruby> 仅主机 <rp>  （ </rp> <rt>  Host-only </rt> <rp>  ） </rp></ruby>网络。然后使用右侧的 `+` 按钮来添加一个新的仅主机网络。

![Set Guest Network](/data/attachment/album/201703/01/134026uapr2c40f0woaob8.png)

*设置客户机网络*

这样就创建好了一个名为 vboxnet0 的新的仅主机模式网卡。

如果你愿意，你可以使用中间的 `-` 按钮来删除这个网卡，你可以单击编辑按钮来查看这个网卡的详细配置信息。

你也可以根据自己的实际环境修改配置信息，比如网络地址，子网掩码等等。

注意：下图中的 IPv4 地址就是你的宿主机的 IP 地址。

![Host Network Details](/data/attachment/album/201703/01/134027a97utfuzolymyxuy.png)

*宿主机网络信息*

下图中，如果你想让客户机使用动态 IP 地址，你可以通过配置 DHCP 服务来完成（在使用前请确保启用 DHCP ）。但是我建议你为客户机配置一个静态 IP 地址。

在下面的设置所有网络界面单击 OK 按钮来保存修改的配置。

![Set Guest Static IP aAddress](/data/attachment/album/201703/01/134027efe11lsfetcfyte1.png)

*为客户机设置静态 IP 地址*

#### 配置客户机网络设置

注意：你可以根据下面的步骤来为任何需要与宿主机通信的客户机添加网卡。

回到 VirtualBox 管理器界面，选择客户机，比如 Ubuntu 16.10 Server 或者 CentOS 7 ，然后单击设置菜单。

![Configure VM Settings](/data/attachment/album/201703/01/134028pii4niiexnwntwuw.png)

*配置客户机网络设置*

#### 配置客户机网卡以连接到宿主机

从下图的界面中选择网络选项。然后配置第一块网卡（ 网卡 1 ）的信息如下：

1. 勾选选项：“启用网卡”来开启该网卡。
2. 在连接方式选项：选择<ruby> 仅主机 <rp>  （ </rp> <rt>  Host-only </rt> <rp>  ） </rp></ruby>网络。
3. 然后选择网卡名称：vboxnet0

如下图所示，单击 OK 来保存设置：

![Enable Network Adapter for Guest VM](/data/attachment/album/201703/01/134028dzxeeqedg7a4dgg6.png)

*启用客户机网络*

#### 配置客户机网卡连接外网

之后添加第二块网卡（网卡 2 ）来让客户机连接到宿主机进行上网。使用下面的设置：

1. 勾选选项：“启用网络连接”来激活这块网卡。
2. 在连接方式选项：选择 NAT 方式。

![Enable Network Adapter for VM](/data/attachment/album/201703/01/134029guxbecc9cmbxkmjr.png)

为客户机启用网络连接

#### 为客户机设置静态 IP 地址

启动客户机，登录系统并[配置静态 IP 地址](http://www.tecmint.com/set-add-static-ip-address-in-linux/)。在客户机上执行下面的命令来查看所有网卡配置信息，然后分配一个 IP 地址：

```
$ ip add

```

![Configure Static IP Address for VM](/data/attachment/album/201703/01/134029r6fe5hooo4t11spp.png)

*为客户机配置静态 IP 地址*

从上面的截图中可以看出客户机已启用了三块网卡：

1. `lo` ——回环网络接口
2. `enp0s3` （网卡 1）—— 前一步我们配置的连接方式为<ruby> 仅主机 <rp>  （ </rp> <rt>  Host-Only </rt> <rp>  ） </rp></ruby>模式并且已启用 DHCP 的网卡，之后我们又配置成了静态 IP 地址。
3. `enp0s8` （网卡 2）—— 用于连接到外网。该网卡默认情况下使用 DHCP 来动态获取 IP 地址。

##### 在 Debian/Ubuntu/Linux Mint 系统下的配置

重要提示: 这里我使用的是 Ubuntu 16.10 Server ， IP 地址为 192.168.56.5 。

使用你喜欢的编辑器及管理员账号打开 `/etc/network/interfaces` 配置文件：

```
$ sudo vi /etc/network/interfaces

```

修改网卡 enp0s3 的配置信息如下（根据你的实际环境进行修改）：

```
auto  enp0s3
iface enp0s3 inet static
address  192.168.56.5
network  192.168.56.0
netmask  255.255.255.0
gateway  192.168.56.1
dns-nameservers  8.8.8.8  192.168.56.1

```

保存并退出。

然后使用下面的命令来重启网络服务：

```
$ sudo systemctl restart networking

```

或者，正常重启系统，然后检查网络配置是否为新的 IP 地址：

```
$ ip add

```

##### 在 RHEL/CentOS/Fedora 系统下的配置

重要提示: 这里我使用的是 CentOS 7 系统， IP 地址为： 192.168.56.10 。

使用管理员帐号及你喜欢的编辑器打开 enp0s3 （仅主机网络）的网卡配置文件 `/etc/sysconfig/network-scripts/ifcfg-enp0s3` ：

```
$ sudo vi /etc/sysconfig/network-scripts/ifcfg-enp0s3

```

创建或修改配置文件信息如下（根据你实际的环境进行修改）：

```
BOOTPROTO=static
ONBOOT=yes
IPADDR=192.168.56.10
NETWORK=192.168.56.0
NETMASK=255.255.255.0
GATEWAY=192.168.56.1
DNS=8.8.8.8 192.168.56.1
NM_CONTROLLED=no     #use this file not network manager to manage interface

```

保存并退出。然后使用下面的命令重启网络服务（也可以重启系统）：

```
$ sudo systemctl restart network.service

```

检查修改的配置地信息是否生效：

```
$ ip add

```

#### 在宿主机上使用 SSH 工具来管理客户机

在宿主机上使用 SSH 工具来管理你的客户机。在下面的实例中，我将使用 SSH 工具连接到 CentOS 7 服务器（192.168.56.10）：

```
$ ssh tecmint@192.168.56.10
$ who

```

![Connect Guest VM using SSH](/data/attachment/album/201703/01/134030dsscy5e0sumiycyy.png)

*使用 SSH 工具连接客户机*

就写到这里吧！在这篇文章中，我们用简单易懂的方法讲述了如何在宿主机与客户机之间设置网络连接方式。请在下面的评论区跟大家分享下你的想法。

---

作者简介：

Aaron Kili 是一名 Linux 和 F.O.S.S 爱好者，即将从事 Linux 系统管理员和网页开发工作，他日前是 TecMint 技术网站的原创作者，非常喜欢使用电脑工作，坚信分享知识是一种美德。

---

via: <http://www.tecmint.com/network-between-guest-vm-and-host-virtualbox/>

作者：[Aaron Kili](http://www.tecmint.com/author/aaronkili/) 译者：[rusking](https://github.com/rusking) 校对：[Bestony](https://github.com/Bestony)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


如何在 Linux 中使用 sFTP 上传或下载文件与文件夹

Summary: sFTP（安全文件传输程序）是一种安全的交互式文件传输程序，其工作方式与 FTP（文件传输协议）类似。 然而，sFTP 比 FTP 更安全；它通过加密 SSH 传输处理所有操作。

scp 完全可以满足日常需求，且非交互

[sFTP（安全文件传输程序）](http://www.tecmint.com/sftp-command-examples/)是一种安全的交互式文件传输程序，其工作方式与 FTP（文件传输协议）类似。 然而，sFTP 比 FTP 更安全；它通过加密 SSH 传输处理所有操作。

![](/data/attachment/album/201703/01/143358dg2eao7t1pzpko00.jpg)

它可以配置使用几个有用的 SSH 功能，如[公钥认证](/article-6901-1.html)和压缩。 它连接并登录到指定的远程机器，然后切换到交互式命令模式，在该模式下用户可以执行各种命令。

在本文中，我们将向你展示如何使用 sFTP 上传/下载整个目录（包括其子目录和子文件）。

### 如何在 Linux 中使用 sFTP 传输文件/文件夹

默认情况下，SFTP 协议采用和 SSH 传输协议一样的方式建立到远程服务器的安全连接。虽然，用户验证使用类似于 SSH 默认设置的密码方式，但是，建议[创建和使用 SSH 无密码登录](/article-6901-1.html)，以简化和更安全地连接到远程主机。

要连接到远程 sftp 服务器，如下建立一个安全 SSH 连接并创建 SFTP 会话：

```
$ sftp tecmint@192.168.56.10

```

登录到远程主机后，你可以如下运行交互式的 sFTP 命令：

```
sftp> ls            #list directory
sftp> pwd           #print working directory on remote host
sftp> lpwd          #print working directory on local host
sftp> mkdir uploads     #create a new directory

```

![Run sFTP Commands on Remote Linux](/data/attachment/album/201703/01/143410x80h1i00l18z81ip.png)

* Linux 主机上运行 sFTP 命令\*

#### 如何使用 sFTP 上传文件夹

要将整个目录上传到远程 Linux 主机中，请使用 `put` 命令。但是，如果目录名称不存在于远程主机上的工作目录中，你将收到错误，如下面的屏幕截图所示。

因此，首先在远程主机上创建一个具有相同名称的目录，然后从本地主机上传它，`-r` 参数允许拷贝子目录和子文件：

```
sftp> put -r  Tecmint.com-articles
sftp> mkdir Tecmint.com-articles
sftp> put -r Tecmint.com-articles

```

![Upload Directory using SFTP](/data/attachment/album/201703/01/143410f7555bz5jm03czn5.png)

*使用 sFTP 上传文件夹*

要保留修改时间、访问时间以及被传输的文件的模式，请使用 `-p` 标志。

```
sftp> put -pr Tecmint.com-articles

```

#### 如何使用 sFTP 下载文件夹

要从远程 Linux 主机下载整个 fstools-0.0 文件夹到本机中，如下所示使用 get 命令带上 `-r` 标志：

```
sftp> get -r fstools-0.0

```

![Download Directory using sFTP](/data/attachment/album/201703/01/143410yp70jjzwp2t9792w.png)

*使用 sFTP 下载目录*

如果文件夹已经下载完成了，接着查看本机的工作目录。

要退出 sFTP shell，输入：

```
sftp> bye
或者
sftp> exit

```

此外，阅读这篇 [sFTP 命令和使用技巧](http://www.tecmint.com/sftp-command-examples/)。

请注意，为了防止用户访问远程主机上的整个文件系统，出于安全原因，你可以使用 chroot Jail [将 sFTP 用户限制到其主目录](http://www.tecmint.com/restrict-sftp-user-home-directories-using-chroot/)中。

就是这样了！在本文中，我们向你展示了如何使用 sFTP 上传/下载整个目录。使用下面的评论栏向我们提供你对本文/主题的看法。

---

作者简介：

Aaron Kili 是 Linux 和 F.O.S.S 爱好者，将来的 Linux 系统管理员和网络开发人员，目前是 TecMint 的内容创作者，他喜欢用电脑工作，并坚信分享知识。

---

via: <http://www.tecmint.com/sftp-upload-download-directory-in-linux/>

作者：[Aaron Kili](http://www.tecmint.com/author/aaronkili/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


在树莓派 3 上运行 openSUSE：简单几步搭建一个实用系统

Summary: 在树莓派 3上部署 openSUSE 系统不是很复杂，不过这儿有一些小技巧教你更好地完成这个过程。

Debian Gnu/Linux 的树梅派有没有全软件包的ISO。方便建立本地源用！！！！

树莓派2是不是不能啊？

应该也可以的

排版出问题了吗？<br />
怎么现在的文章都这么不“漂亮”！

排版出问题了吗？<br />
怎么现在的文章都这么不“漂亮”！

说说你的具体看法？

安装可以正常使用~不过， 读取SD卡的指示灯不亮。

本文由 SUSE 文档团队的技术作者 Dmitri Popov 撰写。

>
> 在[树莓派 3](https://www.raspberrypi.org/)上部署 [openSUSE](https://www.opensuse.org/) 系统不是很复杂，不过这儿有一些小技巧教你更好地完成这个过程。
>
>
>

首先，你将会有一些版本可供选择。如果你打算使用树莓派 3 作为一个普通主机，那么带有图形界面的 openSUSE 将是你最好的选择。有几种不同的图形环境可供选择：[X11](https://www.x.org/wiki/)、[Enlightenment](https://www.enlightenment.org/)、[Xfce](https://www.xfce.org/) 或是 [LXQT](http://lxqt.org/)。openSUSE 还有一个 JeOS 版本能够提供最基础的系统，可以把树莓派 3 作为一个无显示器的服务器使用。更好的选择还有 openSUSE 的 [Leap](https://www.opensuse.org/#Leap) 或 [Tumbleweed](https://www.opensuse.org/#Tumbleweed) 版本。

![](/data/attachment/album/201703/01/235212vo60zoe65re6x5se.jpg)

首先你需要从 <https://en.opensuse.org/HCL:Raspberry_Pi3> 下载所需的 openSUSE 镜像，然后制作一张可启动的 microSD 卡。虽然可以使用命令行工具将下载好的镜像写入 microSD 卡，但 [Etcher](https://etcher.io/) 可以使这个过程更轻松安全。从该项目网站上获取该程序，解压下载的 .zip 文件，并使用以下命令把得到的 .AppImage 文件设置为可执行：

```
chmod +x Etcher-x.x.x-linux-x64.AppImage

```

将 microSD 卡插入电脑，双击运行 Etcher 软件，选择下载好的 .raw.xz 镜像文件，点击 **Flash!** 按钮。然后将显示器和键盘连接到树莓派 3，插入 microSD 卡，启动树莓派。第一次启动时，openSUSE 会自动扩展文件系统以充分利用 microSD 卡上的剩余空间。这时你将看到以下信息：

```
GPT data structures destroyed! You may now partition the disk using fdisk or other utilities
GPT 数据结构被破坏！您需要使用 fdisk 或其它工具对磁盘分区。

```

不用担心，稍等两分钟，openSUSE 将继续正常启动。当看到提示时，输入默认用户名 `root` 和默认密码 `linux` 登录系统。

如果你选择在树莓派 3 上部署 JeOS 版本，第一次启动时你不会看到屏幕上有任何输出。也就是说，屏幕会一直保持空白，直到系统完成对文件系统的扩展。你可以通过配置内核参数来显示输出，不过没有必要做这麻烦事。只需稍等片刻，你就能看到命令行提示。

由于 openSUSE 已经启用并且配置了 SSH 服务，所以启动树莓派时你可以不用显示器。这样的话，你就需要使用网卡接口将树莓派连接到网络中。给树莓派足够的时间来启动和扩展系统后，你就能够从同一网络中的其他主机，使用 `ssh root@linux.local` 命令，通过 SSH 服务连接树莓派。

默认情况下你将以 `root` 用户登录系统，所以创建一个普通用户是个不错的主意。你可以使用 YaST 配置工具轻松完成这件事。运行 `yast2` 命令，选择 <ruby> <strong>  安全与用户 </strong> <rt>  Security and Users </rt></ruby> -> **<ruby> 用户与用户组管理 <rt>  User and Group Management </rt></ruby>** 选项，就可以创建新用户了。你还可以选择 **<ruby> 系统 <rt>  System </rt></ruby>** -> **<ruby> 在线升级 <rt>  Online Update </rt></ruby>**选项来更新系统。完成之后，退出 YaST ，重启树莓派，然后使用新创建的用户登录系统。

一切搞定，不过还有一个重要的系统组件不能正常工作，那就是无线接口。当然，这个问题也可以轻松解决。首先使用以下命令安装 nano 文本编辑器：

```
sudo zypper in nano

```

然后运行以下命令修改 `raspberrypi_modules.conf` 文件：

```
sudo nano/etc/dracut.conf.d/raspberrypi_modules.conf

```

删除文件第一行的 `sdhci_iproc` ，再取消最后一行的注释。运行以下命令保存修改：

```
mkinitrd -f

```

最后，重启树莓派。

![](/data/attachment/album/201703/01/235325dckzyc7grgi642ky.png)

再次运行 YaST ，在**<ruby> 系统 <rt>  System </rt></ruby>** -> **<ruby> 网络设置 <rt>  Network Settings </rt></ruby>**区域，你应该能在网络接口列表中看到 `BCM43430 WLAN Card` 项。选择这一项，点击 **<ruby> 编辑 <rt>  Edit </rt></ruby>** 按钮。开启**<ruby> 动态地址分配 <rt>  Dynamic Address DHCP </rt></ruby>**选项，点击**下一步**，选择你想要连接的无线网络，配置所需的连接设置。点击**下一步**和**确定**保存设置。重启树莓派，它应该就能连接上特定的 Wi-Fi 网络了。

至此，你就完成了树莓派上的系统部署。

---

via: <https://www.suse.com/communities/blog/opensuse-raspberry-pi-3-zero-functional-system-easy-steps/>

作者：[chabowski](https://www.suse.com/communities/blog/author/chabowski/) 译者：[Cathon](https://github.com/Cathon) 校对：[jasminepeng](https://github.com/jasminepeng)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


长期维护嵌入式 Linux 内核变得容易

嵌入式设备根本经不起更新，一更新刷系统失败，产品要寄回，成本大增。一般发货了，就不再提供更新了

的确，系统和应用的代码出现小改动，就有可能发生莫名其妙的问题

![Jan Lübbe ](/data/attachment/album/201703/02/011322tsh9l27fnlezqgzn.png "Jan Lübbe ")

*Pengutronix 内核黑客 Jan Lübbe 总结了嵌入式 Linux 中正在不断增长的安全威胁，并在这次欧洲嵌入式 Linux 会议上概述了一个计划，以保持长期设备的安全和功能完整。* [Linux 基金会](https://www.linux.com/licenses/category/linux-foundation)

安全漏洞只发生在 Windows 上的好日子正在快速过去。恶意软件黑客和拒绝服务老手们正在越来越多地瞄准过时的嵌入式 Linux 设备，因此在 10 月的[<ruby> 欧洲嵌入式 Linux 会议 <rt>  Embedded Linux Conference Europe </rt></ruby>](http://events.linuxfoundation.org/events/archive/2016/embedded-linux-conference-europe)（ELCE）上的几个演讲的主题就与修复 Linux 安全漏洞相关。

最值得去听的讲演之一是 [Pengutronix](http://www.pengutronix.de/index_en.html) 内核黑客 Jan Lübbe 的《长期维护或管理（或免管理）嵌入式系统 10 年以上》。在总结嵌入式 Linux 中不断增长的安全威胁后，Lübbe 制定了一项计划，以确保长期设备的安全和功能完整。 Lübbe 说：“我们需要迁移到更新、更稳定的内核，并进行持续维护以修复关键漏洞。我们需要做上游更新和自动化流程，并建立一个可持续的工作流程。我们没有理由让系统中仍留有过时的软件。”

随着 Linux 设备变得越来越老，传统的生命周期过程已经不再适用。 Lübbe 说：“通常，你会从 SoC 供应商或主线上获取内核、构建系统，并添加到用户空间。你可以定制和添加程序，并做一些测试。但是，在此之后有 15 年的维护阶段，你最好期望平台不会发生变化、不会想要添加新的功能、不需要实施管理调整。”

所有这些变化，越来越多地导致你的系统暴露出新的错误，并需要大量更新以才能与上游软件保持同步。 Lübbe 说：“在内核中发生导致问题的错误并不总是无意的”。对于去年在 Allwinner 内核中发现的[后门](http://arstechnica.com/security/2016/05/chinese-arm-vendor-left-developer-backdoor-in-kernel-for-android-pi-devices/)，他又补充说：“这些供应商的内核从来不会执行主线内核社区的审查流程”。

Lübbe 继续说：“你不能认为你的供应商一直没问题。也许只有一两个工程师查看过后门代码这块。如果补丁发布在 Linux 内核邮件列表上，就不会有这种事，因为总会有人注意到。硬件供应商不关心安全或维护，也许你会在一两年后得到更新，但是即使这样，他们从一个固定版本开始开发，到他们发布稳定的版本通常需要几年的时间。如果你在这个基础上再开始开发，可能又过了半年，这就更过时了。”

越来越多的嵌入式开发人员在<ruby> 长期稳定 <rt>  Long Term Stable </rt></ruby>（LTS）内核上构建长期产品。但这并不意味着没事了。Lübbe 说：“一个产品发布后，人们经常不再遵循稳定的发行链，也不再应用安全补丁。这样你会得到两个最糟糕的结果：过时的内核和没有安全性。你失去了多人测试的好处。”

Lübbe 指出，使用像 Red Hat 这样的面向服务器的发行版的 Pengutronix 客户经常由于快速的定制、需要系统管理员干预的部署和升级系统而遇到问题。

“更新对一些东西有用，特别是在 x86 上，但每个项目基本上是自己建立基础设施来更新到新版本。”

许多开发人员选择把向后移植作为更新长期产品的解决方案。Lübbe 说：“开始时很容易，但是一旦你不处于项目的维护范围，他们就不会告诉你所使用的版本是否受到一个 bug 的影响，因此很难判断一个修复是否相关。于是你不停打补丁和更新，而 bug 也在不断累积，而这些你必须自己维护，因为其他人不使用这些补丁。使用开源软件的好处就丢失了。”

### 跟随上游项目

Lübbe 认为，最好的解决方案是跟踪由上游项目维护的版本。“我们主要关注基于主线内核的开发，所以我们在产品和主流内核及其他上游项目之间尽可能没有差别。长期系统在主线内核上得到很好的支持。大多数不使用 3D 图形的系统只需要很少的补丁。较新的内核版本还有很多[新的强化功能](https://www.linux.com/news/event/ELCE/2017hardening-kernel-protect-against-attackers)，这些可以减少漏洞的影响。

跟随主线发展对许多开发人员来说似乎令人畏惧，但是如果从一开始就这样，然后坚持下去，就会相对容易一些，Lübbe 说：“你需要为系统上做的一切制定流程。你总需要知道什么软件正在运行，这在使用良好的构建系统时会更容易。每个软件版本应定义完整的系统，以便你可以更新相关的一切。如果你不知道那里有什么，你就不能解决它。你也需要一个自动测试和自动部署更新。”

为了“减少更新周期”，Lübbe 建议在开始开发时使用最新的 Linux 内核，并且在进入测试时才转到稳定的内核。之后，他建议每年将系统中的所有软件（包括内核、构建系统、用户空间、glibc 和组件（如 OpenSSL））更新为当年上游项目支持的版本。

Lübbe 说：“得到更新并不意味着你需要部署。如果没有看到安全漏洞，你可以把补丁放在一边，需要时它再用就行。”

最后，Lübbe 建议每个月查看发布公告，并且每周检查 CVE 和主线列表上的安全公告。你只需要问自己“该安全公告是否影响到了你”。他补充说：“如果你的内核足够新，就没有太多的工作。你不会希望通过在新闻中看到你的设备才获得有关你的产品的反馈。”

---

via: <https://www.linux.com/news/event/ELCE/2017/long-term-embedded-linux-maintenance-made-easier>

作者：[ERIC BROWN](https://www.linux.com/users/ericstephenbrown) 译者：[geekpi](https://github.com/geekpi) 校对：[jasminepeng](https://github.com/jasminepeng)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


LXD 2.0 系列（八）：LXD 中的 LXD

这是 LXD 2.0 系列介绍文章的第八篇。

1. [LXD 入门](/article-7618-1.html)
2. [安装与配置](/article-7687-1.html)
3. [你的第一个 LXD 容器](/article-7706-1.html)
4. [资源控制](/article-8072-1.html)
5. [镜像管理](/article-8107-1.html)
6. [远程主机及容器迁移](/article-8169-1.html)
7. [LXD 中的 Docker](/article-8235-1.html)
8. [LXD 中的 LXD](/article-8257-1.html)
9. [实时迁移](/article-8263-1.html)
10. [LXD 和 Juju](/article-8273-1.html)
11. [LXD 和 OpenStack](/article-8274-1.html)
12. [调试，及给 LXD 做贡献](/article-8282-1.html)

![](/data/attachment/album/201703/02/095159je9wsbxxqze97qjw.jpg)

### 介绍

在上一篇文章中，我介绍了如何[在 LXD 中运行 Docker](/article-8235-1.html)，这是一个访问由 Docker 提供的应用程序组合的很好方式，同时 Docker 还运行在 LXD 提供的安全环境中。

我提到的一个情况是为你的用户提供一个 LXD 容器，然后让他们使用他们的容器来运行 Docker。那么，如果他们自己想要在其容器中使用 LXD 运行其他 Linux 发行版，或者甚至允许另一组人来访问运行在他们的容器中的 Linux 系统呢？

原来 LXD 使得用户运行嵌套容器变得非常简单。

### 嵌套 LXD

最简单的情况可以使用 Ubuntu 16.04 镜像来展示。 Ubuntu 16.04 云镜像预装了 LXD。守护进程本身没有运行，因为它是由套接字激活的，所以它不使用任何资源，直到你真正使用它。

让我们启动一个启用了嵌套的 Ubuntu 16.04 容器：

```
lxc launch ubuntu-daily:16.04 c1 -c security.nesting=true

```

你也可以在一个已有的容器上设置 `security.nesting`：

```
lxc config set <container name> security.nesting true

```

或者对所有的容器使用一个指定的配置文件：

```
lxc profile set <profile name> security.nesting true

```

容器启动后，你可以从容器内部得到一个 shell，配置 LXD 并生成一个容器：

```
stgraber@dakara:~$ lxc launch ubuntu-daily:16.04 c1 -c security.nesting=true
Creating c1
Starting c1

stgraber@dakara:~$ lxc exec c1 bash
root@c1:~# lxd init
Name of the storage backend to use (dir or zfs): dir

We detected that you are running inside an unprivileged container.
This means that unless you manually configured your host otherwise,
you will not have enough uid and gid to allocate to your containers.

LXD can re-use your container's own allocation to avoid the problem.
Doing so makes your nested containers slightly less safe as they could
in theory attack their parent container and gain more privileges than
they otherwise would.

Would you like to have your containers share their parent's allocation (yes/no)? yes
Would you like LXD to be available over the network (yes/no)? no
Do you want to configure the LXD bridge (yes/no)? yes
Warning: Stopping lxd.service, but it can still be activated by:
 lxd.socket
LXD has been successfully configured.

root@c1:~# lxc launch ubuntu:14.04 trusty
Generating a client certificate. This may take a minute...
If this is your first time using LXD, you should also run: sudo lxd init

Creating trusty
Retrieving image: 100%
Starting trusty

root@c1:~# lxc list
+--------+---------+-----------------------+----------------------------------------------+------------+-----------+
|  NAME  |  STATE  |         IPV4          |                     IPV6                     |    TYPE    | SNAPSHOTS |
+--------+---------+-----------------------+----------------------------------------------+------------+-----------+
| trusty | RUNNING | 10.153.141.124 (eth0) | fd7:f15d:d1d6:da14:216:3eff:fef1:4002 (eth0) | PERSISTENT | 0         |
+--------+---------+-----------------------+----------------------------------------------+------------+-----------+
root@c1:~#

```

就是这样简单。

### 在线演示服务器

因为这篇文章很短，我想我会花一点时间谈论我们运行中的[演示服务器](https://linuxcontainers.org/lxd/try-it/)。我们今天早些时候刚刚达到了 10000 个会话！

这个服务器基本上只是一个运行在一个相当强大的虚拟机上的正常的 LXD，一个小型的守护进程实现了我们的网站所使用的 REST API。

当你接受服务条款时，将为你创建一个新的 LXD 容器，并启用 `security.nesting`，如上所述。接着你就像使用 `lxc exec` 时一样连接到了那个容器，除了我们使用 websockets 和 javascript 来做这些。

你在此环境中创建的容器都是嵌套的 LXD 容器。如果你想，你可以进一步地嵌套。

我们全范围地使用了 [LXD 资源限制](https://www.stgraber.org/2016/03/26/lxd-2-0-resource-control-412/)，以防止一个用户的行为影响其他用户，并仔细监控服务器的任何滥用迹象。

如果你想运行自己的类似的服务器，你可以获取我们的网站和守护进程的代码：

```
git clone https://github.com/lxc/linuxcontainers.org
git clone https://github.com/lxc/lxd-demo-server

```

### 额外信息

LXD 的主站在： <https://linuxcontainers.org/lxd>

LXD 的 GitHub 仓库： <https://github.com/lxc/lxd>

LXD 的邮件列表： <https://lists.linuxcontainers.org>

LXD 的 IRC 频道： #lxcontainers on irc.freenode.net

---

作者简介：我是 Stéphane Graber。我是 LXC 和 LXD 项目的领导者，目前在加拿大魁北克蒙特利尔的家所在的Canonical 有限公司担任 LXD 的技术主管。

---

via: <https://www.stgraber.org/2016/04/14/lxd-2-0-lxd-in-lxd-812/>

作者：[Stéphane Graber](https://www.stgraber.org/author/stgraber/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 组织翻译，[Linux中国](https://linux.cn/) 荣誉推出


Samba 系列（四）：在 Windows 下管理 Samba4 AD 域管制器 DNS 和组策略

接着前一篇教程写的关于[使用 Windows 10 的 RSAT 工具来管理 Samba4 活动目录架构](/article-8097-1.html)，在这篇文章中我们将学习如何使用微软 DNS 管理器远程管理我们的 Samba AD 域控制器的 DNS 服务器，如何创建 DNS 记录，如何创建反向查找区域以及如何通过组策略管理工具来创建域策略。

![](/data/attachment/album/201703/02/103407nb3hkxosffb4x34x.jpg)

#### 要求

1、 [在 Ubuntu 16.04 系统上使用 Samba4 软件来创建活动目录架构（一）](/article-8065-1.html)

2、 [在 Linux 命令行下管理 Samba4 AD 架构（二）](/article-8070-1.html)

3、 [使用 Windows 10 的 RSAT 工具来管理 Samba4 活动目录架构 （三）](/article-8097-1.html)

### 第 1 步：管理 Samba DNS 服务器

Samba4 AD DC 使用内部的 DNS 解析器模块，该模块在初始化域提供的过程中创建（如果 BIND9 DLZ 模块未指定使用的情况下）。

Samba4 内部的 DNS 模块支持 AD 域控制器所必须的基本功能。有两种方式来管理域 DNS 服务器，直接在命令行下通过 samba-tool 接口来管理，或者使用已加入域的微软工作站中的 RSAT DNS 管理器远程进行管理。

在这篇文章中，我们使用第二种方式来进行管理，因为这种方式很直观，也不容易出错。

1、要使用 RSAT 工具来管理域控制器上的 DNS 服务器，在 Windows 机器上，打开控制面板 -> 系统和安全 -> 管理工具，然后运行 DNS 管理器工具。

当打开这个工具时，它会询问你将要连接到哪台正在运行的 DNS 服务器。选择“使用下面的计算机”，输入域名（IP 地址或 FQDN 地址都可以使用），勾选“现在连接到指定计算机”，然后单击 OK 按钮以开启 Samba DNS 服务。

![Connect Samba4 DNS on Windows](/data/attachment/album/201703/02/103536r9pv8h1rd9m99oc4.png)

*在 Windows 系统上连接 Samba4 DNS 服务器*

2、为了添加一条 DNS 记录（比如我们添加一条指向 LAN 网关的 A 记录），打开 DNS 管理器，找到域正向查找区，在右侧单击右键选择新的主机（A 或 AAAA）。

![Add DNS A Record on Windows](/data/attachment/album/201703/02/103536qh1hces122yu0ly1.png)

*在 Windows 下添加一条 DNS 记录*

3、在打开的新主机窗口界面，输入 DNS 服务器的主机名和 IP 地址。 DNS 管理器工具会自动填写完成 FQDN 地址。填写完成后，点击“添加主机”按钮，之后会弹出一个新的窗口提示你 DNS A 记录已经创建完成。

确保仅为你的网络中[已配置静态 IP](http://www.tecmint.com/set-add-static-ip-address-in-linux/)的资源（设备）添加 DNS A 记录。不要为那些从 DHCP 服务器自动获取 IP 地址或者经常变换 IP 地址的主机添加 DNS A 记录。

![Configure Samba Host on Windows](/data/attachment/album/201703/02/103537np0ol3qo908bl0lp.png)

*在 Windows 系统下配置 Samba 主机*

要更新一条 DNS 记录只需要双击那条记录，然后输入更改即可。要删除一条记录时，只需要在这条记录上单击右键，选择从菜单删除即可。

同样的方式，你也可以为你的域添加其它类型的 DNS 记录，比如说 CNAME 记录（也称为 DNS 别名记录），MX 记录（在邮件服务器上非常有用）或者其它类型的记录（SPE、TXT、SRV 等类型）。

### 第 2 步：创建反向查找区域

默认情况下，Samba4 AD DC 不会自动为你的域添加一个反向查找区域和 PTR 记录，因为这些类型的记录对于域控制器的正常工作来说是无关紧要的。

相反，DNS 反向区和 PTR 记录在一些重要的网络服务中显得非常有用，比如邮件服务，因为这些类型的记录可以用于验证客户端请求服务的身份。

实际上， PTR 记录的功能与标准的 DNS 记录功能相反。客户端知道资源的 IP 地址，然后去查询 DNS 服务器来识别出已注册的 DNS 名字。

4、要创建 Samba AD DC 的反向查找区域，打开 DNS 管理器，在左侧反向查找区域目录上单击右键，然后选择菜单中的新区域。

![Create Reverse Lookup DNS Zone](/data/attachment/album/201703/02/103538pv7r5aoqlvrvr7aa.png)

*创建 DNS 反向查找区域*

5、下一步，单击下一步按钮，然后从区域类型向导中选择主区域（Primary）。

![Select DNS Zone Type](/data/attachment/album/201703/02/103538aiv7uy90sfuhf9h7.png)

*选择 DNS 区域类型*

6、下一步，在 “AD 区域复制范围”中选择复制到该域里运行在域控制器上的所有的 DNS 服务器，选择 “IPv4 反向查找区域”然后单击下一步继续。

![Select DNS for Samba Domain Controller](/data/attachment/album/201703/02/103539ktkt670z7a66tpsn.png)

*为 Samba 域控制器选择 DNS 服务器*

![Add Reverse Lookup Zone Name](/data/attachment/album/201703/02/103539q2lt28nzf8xggbqw.png)

*添加反向查找区域名*

7、下一步，在网络ID 框中输入你的 LAN IP 地址，然后单击下一步继续。

在这个区域内添加的所有资源（设备）的 PTR 记录仅能指向 192.168.1.0/24 网络段。如果你想要为一个不在该网段中的服务器创建一个 PTR 记录（比如邮件服务器位于 10.0.0.0/24 这个网段的时候），那么你还得为那个网段创建一个新的反向查找区域。

![Add IP Address of Reverse Lookup DNS Zone](/data/attachment/album/201703/02/103540p8an115sanq138qa.png)

*添加 DNS 反向查找区域的 IP 地址*

8、在下一个截图中选择“仅允许安全的动态更新”，单击下一步继续，最后单击完成按钮以完成反向查找区域的创建。

![Enable Secure Dynamic Updates](/data/attachment/album/201703/02/103540v9094wdj7ha7sdso.png)

*启用安全动态更新*

![New DNS Zone Summary](/data/attachment/album/201703/02/103540a2szx67oaso4gigo.png)

*新 DNS 区域概览*

9、此时，你已经为你的域环境创建完成了一个有效的 DNS 反向查找区域。为了在这个区域中添加一个 PTR 记录，在右侧右键单击，选择为网络资源创建一个 PTR 记录。

这个时候，我们已经为网关创建了一个指向。为了测试这条记录对于客户端是否添加正确和工作正常，打开命令行提示符执行 `nslookup` 查询资源名，再执行另外一条命令查询 IP 地址。

两个查询都应该为你的 DNS 资源返回正确的结果。

```
nslookup gate.tecmint.lan
nslookup 192.168.1.1
ping gate

```

![Add DNS PTR Record and Query PTR](/data/attachment/album/201703/02/103541fn2gw285fx8g22mn.png)

*添加及查询 PTR 记录*

### 第 3 步：管理域控制策略

10、域控制器最重要的作用就是集中控制系统资源及安全。使用域控制器的域组策略功能很容易实现这些类型的任务。

遗憾的是，在 Samba 域控制器上唯一用来编辑或管理组策略的方法是通过微软的 RSAT GPM 工具。

在下面的实例中，我们将看到通过组策略来实现在 Samba 域环境中为域用户创建一种交互式的登录提示是多么的简单。

要访问组策略控制台，打开控制面板 -> 系统和安全 -> 管理工具，然后打开组策略管理控制台。

展开你的域下面的目录，在默认组策略上右键，选择菜单中的编辑，将出现一个新的窗口。

![Manage Samba Domain Group Policy](/data/attachment/album/201703/02/103541ke88gg2yt8dym1ry.png)

*管理 Samba 域组策略*

11、在组策略管理编辑器窗口中，进入到计算机配置 -> 组策略 -> Windows 设置 -> 安全设置 -> 本地策略 -> 安全选项，你将在右侧看到一个新的选项列表。

在右侧查询并编辑你的定制化设置，参考下图中的两条设置内容。

![Configure Samba Domain Group Policy](/data/attachment/album/201703/02/103542rszqkxazeanz1az4.png)

*配置 Samba 域组策略*

12、这两个条目编辑完成后，关闭所有窗口，打开 CMD 窗口，执行以下命令来强制应用组策略。

```
gpupdate /force

```

![Update Samba Domain Group Policy](/data/attachment/album/201703/02/103542y7718s7e8ss0eeed.png)

*更新 Samba 域组策略*

13、最后，重启你的电脑，当你准备登录进入系统的时候，你就会看到登录提示生效了。

![Samba4 AD Domain Controller Logon Banner](/data/attachment/album/201703/02/103542esajtahiennhoa8j.png)

*Samba4 AD 域控制器登录提示*

就写到这里吧！组策略是一个操作起来很繁琐和很谨慎的主题，在管理系统的过程中你得非常的小心。还有，注意你设置的组策略不会以任何方式应用到已加入域的 Linux 系统中。

---

作者简介：我是一个电脑迷，开源软件及 Linux 系统爱好者，有近4年的 Linux 桌面和服务器系统及 bash 编程经验。

---

via: <http://www.tecmint.com/manage-samba4-dns-group-policy-from-windows/>

作者：[Matei Cezar](http://www.tecmint.com/author/cezarmatei/) 译者：[rusking](https://github.com/rusking) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


我需要在 AGPLv3 许可证下提供源码么？

在天朝，这种协议几乎什么用都没有。

天朝：程序员看别人代码，能算抄吗？

![](/data/attachment/album/201703/02/113152x5zv13io5hbrimrr.png)

[GNU Affero 通用公共许可证版本 3](https://www.gnu.org/licenses/agpl-3.0-standalone.html)（AGPLv3）是与 GPLv3 几乎相同的<ruby> 左版 <rp>  （ </rp> <rt>  copyleft </rt> <rp>  ） </rp></ruby>许可证。两个许可证具有相同的公共版权范围，但在一个重要方面有重大差异。 AGPLv3 的第 13 节规定了 GPLv2 或 GPLv3 中不存在的附加条件：

>
> 在本许可证的其它条款之外，如果你修改了程序，你必须把你修改的版本，给你的那些使用计算机网络远程（如果你的版本支持此类交互）与之交互的用户，明确提供一个通过一些标准或者常规的复制手段，从网络服务器上免费获得与你所修改的版本相匹配的源码的机会。
>
>
>

这个“通过计算机网络远程交互”的范围主要被认为是 SaaS 部署的情形，尽管其实际上读起来的意思超乎了惯例的 SaaS 部署情形。其目标是解决在用户在使用像 Web Services 这样的功能时，其代码没有公开的常规 GPL 协议所暴露出的漏洞。因此，该协议的第 13 节，在 GPLv2 第 3 节以及 GPLv3 和 AGPLv3 第 6 节中包含的目标代码分发的触发要求之外，提供了额外的源代码公开的要求。

常常被误解的是，AGPLv3 第 13 节中的源代码分发要求仅在 AGPLv3 软件已被“你”（例如，提供网络服务的实体）修改的地方才触发。我的理解是，只要“你”不修改 AGPLv3 的代码，许可证就不应该被理解为需要按照第 13 节规定的方式访问相应的源码。如我所见，尽管即使公开许可证中不要求公开的源代码也是一个好主意，但在 AGPL 下许多未修改以及标准部署的软件模块根本不会触发第 13 节。

如何解释 AGPL 的条款和条件，包括 AGPL 软件是否已被修改，可能需要根据具体情况的事实和细节进行法律层面的分析。

---

作者简介：

Jeffrey R. Kaufman 是全球领先的开源软件解决方案提供商 Red Hat 公司的开源 IP 律师。Jeffrey 也是托马斯·杰斐逊法学院的兼职教授。在入职 Red Hat 之前，Jeffrey 曾经担任高通公司的专利顾问，向首席科学家办公室提供开源顾问。Jeffrey 在 RFID、条形码、图像处理和打印技术方面拥有多项专利。

---

via: <https://opensource.com/article/17/1/providing-corresponding-source-agplv3-license>

作者：[Jeffrey Robert Kaufman](https://opensource.com/users/jkaufman) 译者：[geekpi](https://github.com/geekpi) 校对：[Bestony](https://github.com/Bestony)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


这些计算机术语你读对了么？

Summary: 石墨文档在 GitHub 上做了一个有趣的仓库，专门收集了许多中国程序员容易发音错误的单词；还有一位 lexrus 同学根据此库的数据，做了一个开源的 iOS 应用。

读错了一般多……

我也错了一半多....没想到gui竟然是鬼

这 Linux，原来一直读错了。。。呵呵。。

你个鬼哦……<br />
孤以…

Linux 读音不考究，一直照维基百科上的读。

我自认为自己挺注意的了，可还是有不少读错的。

何止这些。。<br />
读错英语的现象应该十分普遍了

Linux 中的 ux 可以念 阿克斯，这是 Linus 本人在电视访谈节目中说的。<br />
Git 念 高it

受这篇文章启发，写了一点脚本

function say {

    mpv http://dict.youdao.com/dictvoice?audio=${1}'&'type=1 > /dev/null

    }

alias say='say $1'

贴入自己的.bashrc，然后打开终端，say linux

 欢迎补充。

PS：原来这里发链接需要登录，这次就不匿名了

读到一半才发现自己读了错误的一列

哈哈哈哈

赞赞赞！<br />
PS.，ID 不错 →_→

友情提示： function say() 后面要有括号。

有function就不用括号了,没有function才要求要有括号

读错了很多。。。。

linux 都没读对还怎么往下看

function say

        set text $argv[1]

        set lang zh

        for i in (echo $argv | sed 's|--*|\\'\n'|g' | sed 's/[ \t]*$//g' | grep -v '^$')

                echo $i | read -l option value

                switch $option

                        case t text

                                set text $value

                        case l lang

                                set lang $value

                        case s speed

                                set speed $value

                end

        end

        if [ $text ]

                mpv "https://fanyi.baidu.com/gettts?lan=$lang&text=$text&spd=$speed&source=web" > /dev/null

        else

                echo "say -t text -l language(zh cte en jp kor pt de spa ru fra) -s speed"

        end

end

fish 版本，用的百度接口，支持普粤英日韩葡德西俄法等多种语言，阿拉伯语和泰语似乎因为编码的原因返回为空

调用方法，打开终端，输入 say "任何你想读音的单词，句子，段落。"

say -t text -l language(zh cte en jp kor pt de spa ru fra) -s speed

很好哦，小编。

作为程序员，我们虽然不像港台剧那样处处用中文混着英文说话，但是在日常的工作学习中还是会大量接触到各种英文单词和缩写。除了一些很普通的英文单词由于某种神秘不可知的原因被屡屡错读之外，计算机世界是变化的如此之快，也经常出现一些新的缩写令非英语母语的中国程序员在懵圈之后随便选个姿势去读。

![](/data/attachment/album/201703/02/154335utt9mn3it1m3jmzk.jpg)

我猜测，或许是为了将广告野生程序员从这种尴尬之中解脱出来，[石墨文档](https://shimo.im/doc/G3ckHEVF3f4qANHk)在 GitHub 上做了一个有趣的[仓库](https://github.com/shimohq/chinese-programmer-wrong-pronunciation)，专门收集了许多中国程序员容易发音错误的单词。

| 单词 | 正确发音 | 错误发音 |
| --- | --- | --- |
| **access [♪](http://dict.youdao.com/dictvoice?audio=access&type=1)** | **✓ ['ækses]** | **✗ [ək'ses]** |
| **Angular [♪](http://dict.youdao.com/dictvoice?audio=Angular&type=1)** | **✓ ['æŋgjʊlə]** | **✗ ['æŋɡələ; 'æŋdʒʌlə]** |
| **AJAX [♪](http://dict.youdao.com/dictvoice?audio=AJAX&type=1)** | **✓ ['eidʒæks]** | **✗ [ə'dʒʌks]** |
| **Apache [♪](http://dict.youdao.com/dictvoice?audio=Apache&type=1)** | **✓ [ə'pætʃɪ]** | **✗ [ʌpʌtʃ]** |
| **app [♪](http://dict.youdao.com/dictvoice?audio=app&type=1)** | **✓ [æp]** |  |
| **archive [♪](http://dict.youdao.com/dictvoice?audio=archive&type=1)** | **✓ ['ɑːkaɪv]** | **✗ ['ətʃɪv]** |
| **array [♪](http://dict.youdao.com/dictvoice?audio=array&type=1)** | **✓ [ə'rei]** | **✗ [æ'rei]** |
| **avatar [♪](http://dict.youdao.com/dictvoice?audio=avatar&type=1)** | **✓ ['ævətɑː]** | **✗ [ə'vʌtɑ]** |
| **Azure [♪](http://dict.youdao.com/dictvoice?audio=azure&type=1)** | **✓ ['æʒə]** | **✗ [ˈæzʊʒə]** |
| **cache [♪](http://dict.youdao.com/dictvoice?audio=cache&type=1)** | **✓ [kæʃ]** | **✗ [kætʃ]** |
| **deque [♪](http://dict.youdao.com/dictvoice?audio=deque&type=1)** | **✓ ['dek]** | **✗ [di'kju]** |
| **digest [♪](http://dict.youdao.com/dictvoice?audio=digest&type=1)** | **✓ ['dɑɪdʒɛst]** | **✗ ['dɪgɛst]** |
| **Django [♪](http://dict.youdao.com/dictvoice?audio=Django&type=1)** | **✓ [ˈdʒæŋɡoʊ]** | **✗ [diˈdʒæŋɡoʊ]** |
| **doc [♪](http://dict.youdao.com/dictvoice?audio=doc&type=1)** | **✓ [dɒk]** | **✗ [daʊk]** |
| **facade [♪](http://dict.youdao.com/dictvoice?audio=facade&type=1)** | **✓ [fə'sɑːd]]** | **✗ ['feikeid]** |
| **Git [♪](http://dict.youdao.com/dictvoice?audio=git&type=1)** | **✓ [ɡɪt]** | **✗ [dʒɪt; jɪt]** |
| **GNU [♪](http://dict.youdao.com/dictvoice?audio=GNU&type=1)** | **✓ [gnu:]** |  |
| **GUI [♪](http://dict.youdao.com/dictvoice?audio=GUI&type=1)** | **✓ [ˈɡui]** |  |
| **height [♪](http://dict.youdao.com/dictvoice?audio=height&type=1)** | **✓ [haɪt]** | **✗ [heɪt]** |
| **hidden [♪](http://dict.youdao.com/dictvoice?audio=hidden&type=1)** | **✓ ['hɪdn]** | **✗ ['haɪdn]** |
| **image [♪](http://dict.youdao.com/dictvoice?audio=image&type=1)** | **✓ ['ɪmɪdʒ]** | **✗ [ɪ'meɪdʒ]** |
| **integer [♪](http://dict.youdao.com/dictvoice?audio=integer&type=1)** | **✓ ['ɪntɪdʒə]** | **✗ [ˈɪntaɪgə]** |
| **issue [♪](http://dict.youdao.com/dictvoice?audio=issue&type=1)** | **✓ ['ɪʃuː]** | **✗ [ˈaɪʃuː]** |
| **Java [♪](http://dict.youdao.com/dictvoice?audio=java&type=1)** | **✓ ['dʒɑːvə]** | **✗ ['dʒɑːvɑː]** |
| **jpg(jpeg) [♪](http://dict.youdao.com/dictvoice?audio=JPEG&type=1)** | **✓ ['dʒeɪpeɡ]** | **[ˈdʒeɪˈpi:ˈdʒiː]** |
| **Linux [♪](http://dict.youdao.com/dictvoice?audio=linux&type=1)** | **✓ ['lɪnəks]** | **✗ [ˈlɪnʌks; ˈlɪnjuːks]** |
| **main [♪](http://dict.youdao.com/dictvoice?audio=main&type=1)** | **✓ [meɪn]** | **✗ [mɪn]** |
| **margin [♪](http://dict.youdao.com/dictvoice?audio=margin&type=1)** | **✓ ['mɑːdʒɪn]** | **✗ ['mʌgɪn]** |
| **maven [♪](http://dict.youdao.com/dictvoice?audio=maven&type=1)** | **✓ ['meɪvn]** | **✗ ['maːvn]** |
| **module [♪](http://dict.youdao.com/dictvoice?audio=module&type=1)** | **✓ ['mɒdjuːl]** | **✗ ['məʊdl]** |
| **nginx** | **✓ Engine X** |  |
| **null [♪](http://dict.youdao.com/dictvoice?audio=null&type=1)** | **✓ [nʌl]** | **✗ [naʊ]** |
| **OS X** | **✓ OS ten** |  |
| **parameter [♪](http://dict.youdao.com/dictvoice?audio=parameter&type=1)** | **✓ [pə'ræmɪtə]** | **✗ ['pærəmɪtə]** |
| **putty [♪](http://dict.youdao.com/dictvoice?audio=putty&type=1)** | **✓ [ˈpʌti]** | **✗ [ˈpuːti]** |
| **query [♪](http://dict.youdao.com/dictvoice?audio=query&type=1)** | **✓ ['kwɪəri]** | **✗ ['kwaɪri]** |
| **Qt [♪](http://dict.youdao.com/dictvoice?audio=cute&type=1)** | **✓ [kjuːt]** |  |
| **resolved [♪](http://dict.youdao.com/dictvoice?audio=resolved&type=1)** | **✓ [rɪ'zɒlvd]** | **✗ [rɪ'səʊvd]** |
| **retina [♪](http://dict.youdao.com/dictvoice?audio=retina&type=1)** | **✓ ['retɪnə]** | **✗ [ri'tina]** |
| **san jose [♪](http://dict.youdao.com/dictvoice?audio=san%20jose&type=1)** | **✓ [sænhəu'zei]** | **✗ [sæn'ju:s]** |
| **safari [♪](http://dict.youdao.com/dictvoice?audio=safari&type=1)** | **✓ [sə'fɑːrɪ]** | **✗ [sæfərɪ]** |
| **scheme [♪](http://dict.youdao.com/dictvoice?audio=scheme&type=1)** | **✓ [skiːm]** | **✗ [s'kæmə]** |
| **sudo** | **✓ ['suːduː]** |  |
| **suite [♪](http://dict.youdao.com/dictvoice?audio=suite&type=1)** | **✓ [swiːt]** | **✗ [sjuːt]** |
| **typical [♪](http://dict.youdao.com/dictvoice?audio=typical&type=1)** | **✓ ['tɪpɪkl]** | **✗ ['taɪpɪkəl]** |
| **Ubuntu [♪](http://dict.youdao.com/dictvoice?audio=ubuntu&type=1)** | **✓ [ʊ'bʊntʊ]** | **✗ [juː'bʊntʊ]** |
| **variable [♪](http://dict.youdao.com/dictvoice?audio=variable&type=1)** | **✓ ['veəriəbl]** | **✗ [və'raiəbl]** |
| **vue [♪](http://dict.youdao.com/dictvoice?audio=vue&type=1)** | **✓ [v'ju:]** | **✗ [v'ju:i]** |
| **width [♪](http://dict.youdao.com/dictvoice?audio=width&type=1)** | **✓ [wɪdθ]** | **✗ [waɪdθ]** |
| **YouTube [♪](http://dict.youdao.com/dictvoice?audio=youtube&type=1)** | **✓ ['juː'tjuːb]** | **✗ ['juː'tʊbɪ]** |

本着简单的原则，又为了避免程序员们出现选择困难症，“正确音标”采用了最接近有道词典音频的英式 DJ 音标，不代表其唯一性。专业在线英语词典请参考[知乎链接:在线英语词典哪个比较好?](https://www.zhihu.com/question/19707759)

参考资料

1. <https://www.zhihu.com/question/19739907>
2. <https://www.v2ex.com/t/131094>
3. <https://www.v2ex.com/t/309350>
4. <https://www.v2ex.com/t/63781>
5. <https://www.v2ex.com/t/246033>
6. <https://www.v2ex.com/t/342087>

此外，还有一位 [lexrus](https://github.com/lexrus) 同学根据此库的数据，做了一个开源的 iOS 应用，这个应用的名字非常绝妙，叫做“[花灰](https://github.com/lexrus/Huahui)”——请捋直了舌头跟我念：H-U-A-花，H-U-I-灰，花灰。

作者说道：

>
> 这个小 App 用 UITableView 陈列了一些中国程序员经常读错的单词，用 AVFoundation 实现美音和英音的朗读，用 Playground 同步单词库。功能异常简陋，实现非常粗暴，仅供 iOS 程序员自行编译，纠正发音使用，懒得提交 App Store。
>
>
>

可从以下视频中略窥一斑，做的还是不错的，不过如果你的 iPhone 没越狱，显然是没法用的——或许你可以去作者的 issues 页面请求他上架？:-D

那么你有几个读错的呢？欢迎发表评论，也欢迎去[提交](https://github.com/shimohq/chinese-programmer-wrong-pronunciation)你发现易读错的计算机术语和缩写。

有同学表示，读对还不行，你经常用的一些单词术语，或许并不是你以为的那个意思。不信？你去看看“[程序员眼中的英语单词](/article-6302-1.html)”和它原本的意思差别有多大就知道了~


将 Tuleap 用于软件项目管理

Summary: Eclipse 基金会使用 Tuleap 取代了 Bugzilla。

>
> [Eclipse 基金会](http://www.eclipse.org/)使用 Tuleap 取代了 Bugzilla。
>
>
>

![](/data/attachment/album/201703/03/100509jfraleff4fdttf0t.jpg)

Tuleap 是一个独特的开源项目管理工具，目前发展势头很好，现在，每个月它会出一个大版本。它还被列在 [2015 年五大开源项目管理工具](https://opensource.com/business/15/1/top-project-management-tools-2015)和 [2016 年十一个名列前茅项目管理工具](https://opensource.com/business/16/3/top-project-management-tools-2016)中。

Manuel Vacelet 是开发 Tuleap 项目的 Enalean 公司的联合创始人和 CTO，他说：“Tuleap 是一个完整用于托管软件项目的 GPLv2 平台，它提供了一个集中化的平台，在这里，团队可以找到他们所需的所有工具，追踪他们软件项目的生命周期。他们可以找到项目管理（Scrum、看板、瀑布、混合等等）、源码控制（git 和 svn）和代码审查（pull 请求和 gerrit）、持续集成、问题跟踪、wiki 和文档等的支持。”

![](/data/attachment/album/201703/03/100629pwnlg5mz5pvjnmvg.png)

![](/data/attachment/album/201703/03/100629xk99h3oksoc7yh0s.png)

在这次采访中，我会和 Manuel 讨论如何开始使用它，以及如何以开源方式管理 Tuleap。

**Nitish Tiwari（以下简称 NT）： 为什么 Tuleap 项目很重要？**

**Manuel Vacelet（以下简称 MV）：** Tuleap 很重要是因为我们坚信一个成功的（软件）项目必须涉及所有利益相关者：开发人员、项目经理、QA、客户和用户。

很久以前，我还是一个 SourceForge 衍生项目的实习生（当时 SourceForge 还是一个自由开源项目），几年后它变成了 Tuleap。 我的第一个贡献是将 PhpWiki 集成到该工具中（不要告诉任何人，代码写的很糟）。

现在，我很高兴作为首席技术官和产品负责人在 Enalean 工作，该公司是 Tuleap 项目的主要贡献公司。

**NT：让我们聊聊技术方面。**

**MV：** Tuleap 核心系统是基于 LAMP 并且架构于 CentOS 之上。如今的开发栈是 AngularJS (v1)、REST 后端（PHP）、基于 NodeJS 的实时推送服务器。但如果你想成为一名 Tuleap 全栈开发人员，你还将需要接触 bash、Perl、Python、Docker、Make 等等。

说到技术方面，需要重点强调的 Tuleap 的一个显著特征是它的可扩展性。一个运行在单服务器上的 Tuleap 单一实例、并且没有复杂的 IT 架构，可以处理超过 10000 人的访问。

**NT：给我们说下该项目的用户和社区。有谁参与？他们如何使用这个工具？**

**MV：** 用户非常多样化。从使用 Tuleap 跟踪他们的项目进度并管理他们的源代码的小型初创公司，到非常大的公司，如法国电信运营商 Orange，它为超过 17000 用户部署了它，并托管了 5000 个项目。

许多用户依靠 Tuleap 来促进敏捷项目并跟踪其进度。开发人员和客户共享同一个工作区。客户不需要学习如何使用 GitHub，也不需要开发人员做额外的工作，就可以将其工作转换到“客户可访问”平台。

今年， [Eclipse 基金会](http://www.eclipse.org/)使用 Tuleap 取代了 Bugzilla。

印度电子信息技术部使用 Tuleap 创建了印度政府开放电子政务的开放式协作开发平台。

Tuleap 有许多种不同的使用方式和配置。有些人使用它作为 Drupal 客户门户网站的后端; 它们通过 REST API 插入到 Tuleap 中以管理 bug 和服务请求。

甚至一些建筑师也使用它来管理他们的工作进度和 AutoCAD 文件。

**NT：Tuleap 是否做了一些特别的事，使社区更安全，更多样化？**

**MV：** 我们还没有创建“行为准则”；本社区非常平和而欢迎新人，但我们有计划这样做。Tuleap 的开发人员和贡献者来自不同的国家（例如加拿大、突尼斯、法国）。而且 35％ 的活跃开发者和贡献者是女性。

**NT：由社区提议的 Tuleap 功能的百分比是多少？**

**MV：** 几乎 100％ 的功能是由社区驱动的。

这是 Enalean 的关键挑战之一：找到一种商业模式，使我们能以正确的方式做开源软件。对我们来说，“开放核心”模式（其中应用程序的核心是开放的，但有趣和有用的部分是封闭源的）不是正确的方法，因为你最终还是要依赖闭源。因此，我们发明了 [OpenRoadmap](https://blog.enalean.com/enalean-open-roadmap-how-it-works/)，这种方式是我们从社区和最终用户那里收集需求，并找公司来为此买单。

---

作者简介：

Nitish 是一名专业的软件开发人员并对开源有热情。作为一本基于 Linux 的杂志的技术作者，他会尝试新的开源工具。他喜欢阅读和探索任何开源相关的事情。在他的空闲时间，他喜欢读励志书。他目前正在构建 DevUp - 一个让开发人员以真正的方式连接所有工具和拥抱 DevOps 的平台。你可以在 Twitter 上关注他 @tiwari\_nitish。

---

via: <https://opensource.com/article/17/1/interview-Tuleap-project>

作者：[Nitish Tiwari](https://opensource.com/users/tiwarinitish86) 译者：[geekpi](https://github.com/geeki) 校对：[jamsinepeng](https://github.com/jasminepeng)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


在 Atomic 主机上远程使用 Docker

Summary: 来自 Atomic 项目 的 Atomic 主机是一个轻量级的容器基于的操作系统，它可以运行 Linux 容器。它已被优化为用作云环境的容器运行时系统。

![remote-atomic-docker](/data/attachment/album/201703/03/103449ryt3tg43yhgvh212.jpg)

来自 [Atomic 项目](http://www.projectatomic.io/) 的 Atomic 主机是一个轻量级的容器基于的操作系统，它可以运行 Linux 容器。它已被优化为用作云环境的容器运行时系统。例如，它可以托管 Docker 守护进程和容器。有时，你可能需要在该主机上运行 docker 命令，并从其他地方管理服务器。本文介绍如何远程访问 Fedora Atomic 主机（[你可以在这里下载到它](https://getfedora.org/atomic/)）上的 [Docker](https://www.docker.com/) 守护进程。整个过程由 [Ansible](https://www.ansible.com/) 自动完成 - 在涉及到自动化的一切上，这真是一个伟大的工具！

### 安全备忘录

由于我们通过网络连接，所以我们使用 [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security) 保护 Docker 守护进程。此过程需要客户端证书和服务器证书。OpenSSL 包用于创建用于建立 TLS 连接的证书密钥。这里，Atomic 主机运行守护程序，我们的本地的 [Fedora Workstation](https://getfedora.org/en/workstation/) 充当客户端。

在你按照这些步骤进行之前，请注意，*任何*在客户端上可以访问 TLS 证书的进程在服务器上具有**完全的 root 访问权限**。 因此，客户端可以在服务器上做任何它想做的事情。我们需要仅向可信任的特定客户端主机授予证书访问权限。你应该将客户端证书仅复制到完全由你控制的客户端主机。但即使在这种情况下，客户端机器的安全也至关重要。

不过，此方法只是远程访问守护程序的一种方法。编排工具通常提供更安全的控制。下面的简单方法适用于个人实验，可能不适合开放式网络。

### 获取 Ansible role

[Chris Houseknecht](https://twitter.com/CHouseknecht) 写了一个 Ansible role，它会创造所需的所有证书。这样，你不需要手动运行 `openssl` 命令了。 这些在 [Ansible role 仓库](https://github.com/ansible/role-secure-docker-daemon)中提供。将它克隆到你当前的工作主机。

```
$ mkdir docker-remote-access
$ cd docker-remote-access
$ git clone https://github.com/ansible/role-secure-docker-daemon.git

```

### 创建配置文件

接下来，你必须创建 Ansible 配置文件、<ruby> 清单 <rp>  （ </rp> <rt>  inventory </rt> <rp>  ） </rp></ruby>和<ruby> 剧本 <rp>  （ </rp> <rt>  playbook </rt> <rp>  ） </rp></ruby>文件以设置客户端和守护进程。以下说明在 Atomic 主机上创建客户端和服务器证书。然后，获取客户端证书到本地。最后，它们会配置守护进程以及客户端，使它们能彼此交互。

这里是你需要的目录结构。如下所示，创建下面的每个文件。

```
$ tree docker-remote-access/
docker-remote-access/
├── ansible.cfg
├── inventory
├── remote-access.yml
└── role-secure-docker-daemon

```

`ansible.cfg`：

```
$ vim ansible.cfg

```

```
[defaults]
inventory=inventory

```

清单文件（`inventory`）：

```
$ vim inventory

```

```
[daemonhost]
'IP_OF_ATOMIC_HOST' ansible_ssh_private_key_file='PRIVATE_KEY_FILE'

```

将清单文件（`inventory`） 中的 `IP_OF_ATOMIC_HOST` 替换为 Atomic 主机的 IP。将 `PRIVATE_KEY_FILE` 替换为本地系统上的 SSH 私钥文件的位置。

剧本文件（`remote-access.yml`）：

```
$ vim remote-access.yml

```

```
- name: Docker Client Set up
  hosts: daemonhost
  gather_facts: no
  tasks:
    - name: Make ~/.docker directory for docker certs
      local_action: file path='~/.docker' state='directory'

    - name: Add Environment variables to ~/.bashrc
      local_action: lineinfile dest='~/.bashrc' line='export DOCKER_TLS_VERIFY=1\nexport DOCKER_CERT_PATH=~/.docker/\nexport DOCKER_HOST=tcp://{{ inventory_hostname }}:2376\n' state='present'

    - name: Source ~/.bashrc file
      local_action: shell source ~/.bashrc

- name: Docker Daemon Set up
  hosts: daemonhost
  gather_facts: no
  remote_user: fedora
  become: yes
  become_method: sudo
  become_user: root
  roles:
    - role: role-secure-docker-daemon
      dds_host: "{{ inventory_hostname }}"
      dds_server_cert_path: /etc/docker
      dds_restart_docker: no
  tasks:
    - name: fetch ca.pem from daemon host
      fetch:
        src: /root/.docker/ca.pem
        dest: ~/.docker/
        fail_on_missing: yes
        flat: yes
    - name: fetch cert.pem from daemon host
      fetch:
        src: /root/.docker/cert.pem
        dest: ~/.docker/
        fail_on_missing: yes
        flat: yes
    - name: fetch key.pem from daemon host
      fetch:
        src: /root/.docker/key.pem
        dest: ~/.docker/
        fail_on_missing: yes
        flat: yes
    - name: Remove Environment variable OPTIONS from /etc/sysconfig/docker
      lineinfile:
        dest: /etc/sysconfig/docker
        regexp: '^OPTIONS'
        state: absent

    - name: Modify Environment variable OPTIONS in /etc/sysconfig/docker
      lineinfile:
        dest: /etc/sysconfig/docker
        line: "OPTIONS='--selinux-enabled --log-driver=journald --tlsverify --tlscacert=/etc/docker/ca.pem --tlscert=/etc/docker/server-cert.pem --tlskey=/etc/docker/server-key.pem -H=0.0.0.0:2376 -H=unix:///var/run/docker.sock'"
        state: present

    - name: Remove client certs from daemon host
      file:
        path: /root/.docker
        state: absent

    - name: Reload Docker daemon
      command: systemctl daemon-reload
    - name: Restart Docker daemon
      command: systemctl restart docker.service

```

### 访问 Atomic 主机

现在运行 Ansible 剧本：

```
$ ansible-playbook remote-access.yml

```

确保 tcp 端口 2376 在你的 Atomic 主机上打开了。如果你在使用 Openstack，请在安全规则中添加 TCP 端口 2376。 如果你使用 AWS，请将其添加到你的安全组。

现在，在你的工作站上作为普通用户运行的 `docker` 命令与 Atomic 主机的守护进程通信，并在那里执行命令。你不需要手动 `ssh` 或在 Atomic 主机上发出命令。这可以让你远程、轻松、安全地启动容器化应用程序。

如果你想克隆 Ansible 剧本和配置文件，这里是 [git 仓库](https://github.com/trishnaguha/fedora-cloud-ansible/tree/master/docker-remote-access)。

![docker-daemon](/data/attachment/album/201703/03/103449g9dsp93n2esseww9.jpg)

---

via: <https://fedoramagazine.org/use-docker-remotely-atomic-host/>

作者：[Trishna Guha](http://trishnag.id.fedoraproject.org/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


LXD 2.0 系列（九）：实时迁移

这是 LXD 2.0 系列介绍文章的第九篇。

1. [LXD 入门](/article-7618-1.html)
2. [安装与配置](/article-7687-1.html)
3. [你的第一个 LXD 容器](/article-7706-1.html)
4. [资源控制](/article-8072-1.html)
5. [镜像管理](/article-8107-1.html)
6. [远程主机及容器迁移](/article-8169-1.html)
7. [LXD 中的 Docker](/article-8235-1.html)
8. [LXD 中的 LXD](/article-8257-1.html)
9. [实时迁移](/article-8263-1.html)
10. [LXD 和 Juju](/article-8273-1.html)
11. [LXD 和 OpenStack](/article-8274-1.html)
12. [调试，及给 LXD 做贡献](/article-8282-1.html)

![](/data/attachment/album/201703/04/101550m3kxz4fjxpoyo6pn.jpg)

### 介绍

LXD 2.0 中的有一个尽管是实验性质的但非常令人兴奋的功能，那就是支持容器检查点和恢复。

简单地说，检查点/恢复意味着正在运行的容器状态可以被序列化到磁盘，要么可以作为同一主机上的有状态快照，要么放到另一主机上相当于实时迁移。

### 要求

要使用容器实时迁移和有状态快照，你需要以下条件：

* 一个非常新的 Linux 内核，4.4 或更高版本。
* CRIU 2.0，可能需要一些 cherry-pick 的提交，具体取决于你确切的内核配置。
* 直接在主机上运行 LXD。 不能在容器嵌套下使用这些功能。
* 对于迁移，目标主机必须至少实现源主机的指令集，目标主机内核必须至少提供与源主机相同的系统调用，并且在源主机上挂载的任何内核文件系统也必须可挂载到目标主机上。

Ubuntu 16.04 LTS 已经提供了所有需要的依赖，在这种情况下，您只需要安装 CRIU 本身：

```
apt install criu

```

### 使用 CRIU

#### 有状态快照

一个普通的快照看上去像这样：

```
stgraber@dakara:~$ lxc snapshot c1 first
stgraber@dakara:~$ lxc info c1 | grep first
 first (taken at 2016/04/25 19:35 UTC) (stateless)

```

一个有状态快照看上去像这样：

```
stgraber@dakara:~$ lxc snapshot c1 second --stateful
stgraber@dakara:~$ lxc info c1 | grep second
 second (taken at 2016/04/25 19:36 UTC) (stateful)

```

这意味着所有容器运行时状态都被序列化到磁盘并且作为了快照的一部分。可以像你还原无状态快照那样还原一个有状态快照：

```
stgraber@dakara:~$ lxc restore c1 second
stgraber@dakara:~$

```

#### 有状态快照的停止/启动

比方说你由于升级内核或者其他类似的维护而需要重启机器。与其等待重启后启动所有的容器，你可以：

```
stgraber@dakara:~$ lxc stop c1 --stateful

```

容器状态将会写入到磁盘，会在下次启动时读取。

你甚至可以看到像下面那样的状态：

```
root@dakara:~# tree /var/lib/lxd/containers/c1/rootfs/state/
/var/lib/lxd/containers/c1/rootfs/state/
├── cgroup.img
├── core-101.img
├── core-102.img
├── core-107.img
├── core-108.img
├── core-109.img
├── core-113.img
├── core-114.img
├── core-122.img
├── core-125.img
├── core-126.img
├── core-127.img
├── core-183.img
├── core-1.img
├── core-245.img
├── core-246.img
├── core-50.img
├── core-52.img
├── core-95.img
├── core-96.img
├── core-97.img
├── core-98.img
├── dump.log
├── eventfd.img
├── eventpoll.img
├── fdinfo-10.img
├── fdinfo-11.img
├── fdinfo-12.img
├── fdinfo-13.img
├── fdinfo-14.img
├── fdinfo-2.img
├── fdinfo-3.img
├── fdinfo-4.img
├── fdinfo-5.img
├── fdinfo-6.img
├── fdinfo-7.img
├── fdinfo-8.img
├── fdinfo-9.img
├── fifo-data.img
├── fifo.img
├── filelocks.img
├── fs-101.img
├── fs-113.img
├── fs-122.img
├── fs-183.img
├── fs-1.img
├── fs-245.img
├── fs-246.img
├── fs-50.img
├── fs-52.img
├── fs-95.img
├── fs-96.img
├── fs-97.img
├── fs-98.img
├── ids-101.img
├── ids-113.img
├── ids-122.img
├── ids-183.img
├── ids-1.img
├── ids-245.img
├── ids-246.img
├── ids-50.img
├── ids-52.img
├── ids-95.img
├── ids-96.img
├── ids-97.img
├── ids-98.img
├── ifaddr-9.img
├── inetsk.img
├── inotify.img
├── inventory.img
├── ip6tables-9.img
├── ipcns-var-10.img
├── iptables-9.img
├── mm-101.img
├── mm-113.img
├── mm-122.img
├── mm-183.img
├── mm-1.img
├── mm-245.img
├── mm-246.img
├── mm-50.img
├── mm-52.img
├── mm-95.img
├── mm-96.img
├── mm-97.img
├── mm-98.img
├── mountpoints-12.img
├── netdev-9.img
├── netlinksk.img
├── netns-9.img
├── netns-ct-9.img
├── netns-exp-9.img
├── packetsk.img
├── pagemap-101.img
├── pagemap-113.img
├── pagemap-122.img
├── pagemap-183.img
├── pagemap-1.img
├── pagemap-245.img
├── pagemap-246.img
├── pagemap-50.img
├── pagemap-52.img
├── pagemap-95.img
├── pagemap-96.img
├── pagemap-97.img
├── pagemap-98.img
├── pages-10.img
├── pages-11.img
├── pages-12.img
├── pages-13.img
├── pages-1.img
├── pages-2.img
├── pages-3.img
├── pages-4.img
├── pages-5.img
├── pages-6.img
├── pages-7.img
├── pages-8.img
├── pages-9.img
├── pipes-data.img
├── pipes.img
├── pstree.img
├── reg-files.img
├── remap-fpath.img
├── route6-9.img
├── route-9.img
├── rule-9.img
├── seccomp.img
├── sigacts-101.img
├── sigacts-113.img
├── sigacts-122.img
├── sigacts-183.img
├── sigacts-1.img
├── sigacts-245.img
├── sigacts-246.img
├── sigacts-50.img
├── sigacts-52.img
├── sigacts-95.img
├── sigacts-96.img
├── sigacts-97.img
├── sigacts-98.img
├── signalfd.img
├── stats-dump
├── timerfd.img
├── tmpfs-dev-104.tar.gz.img
├── tmpfs-dev-109.tar.gz.img
├── tmpfs-dev-110.tar.gz.img
├── tmpfs-dev-112.tar.gz.img
├── tmpfs-dev-114.tar.gz.img
├── tty.info
├── unixsk.img
├── userns-13.img
└── utsns-11.img

0 directories, 154 files

```

还原容器也很简单：

```
stgraber@dakara:~$ lxc start c1

```

### 实时迁移

实时迁移基本上与上面的有状态快照的停止/启动相同，除了容器目录和配置被移动到另一台机器上。

```
stgraber@dakara:~$ lxc list c1
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| NAME |  STATE  |          IPV4         |                     IPV6                     |    TYPE    | SNAPSHOTS |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| c1   | RUNNING | 10.178.150.197 (eth0) | 2001:470:b368:4242:216:3eff:fe19:27b0 (eth0) | PERSISTENT | 2         |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+

stgraber@dakara:~$ lxc list s-tollana:
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

stgraber@dakara:~$ lxc move c1 s-tollana:

stgraber@dakara:~$ lxc list c1
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

stgraber@dakara:~$ lxc list s-tollana:
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| NAME |  STATE  |          IPV4         |                     IPV6                     |    TYPE    | SNAPSHOTS |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+
| c1   | RUNNING | 10.178.150.197 (eth0) | 2001:470:b368:4242:216:3eff:fe19:27b0 (eth0) | PERSISTENT | 2         |
+------+---------+-----------------------+----------------------------------------------+------------+-----------+

```

### 限制

正如我之前说的，容器的检查点/恢复还是非常新的功能，我们还在努力地开发这个功能、修复已知的问题。我们确实需要更多的人来尝试这个功能，并给我们反馈，但我不建议在生产中使用这个功能。

我们跟踪的问题列表在 [Launchpad上](https://bugs.launchpad.net/ubuntu/+source/criu/+bugs)。

我们估计在带有 CRIU 的 Ubuntu 16.04 上带有几个服务的基本的 Ubuntu 容器能够正常工作。然而在更复杂的容器、使用了设备直通、复杂的网络服务或特殊的存储配置下可能会失败。

要是有问题，CRIU 会尽可能地在转储时失败，而不是在恢复时。在这种情况下，源容器将继续运行，快照或迁移将会失败，并生成一个日志文件用于调试。

在极少数情况下，CRIU 无法恢复容器，在这种情况下，源容器仍然存在但将被停止，并且必须手动重新启动。

### 发送 bug 报告

我们正在跟踪 Launchpad 上关于 CRIU Ubuntu 软件包的检查点/恢复相关的错误。大多数修复 bug 工作是在上游的 CRIU 或 Linux 内核上进行，但是这种方式我们更容易跟踪。

要提交新的 bug 报告，请看这里。

请务必包括：

* 你运行的命令和显示给你的错误消息
* `lxc info` 的输出（\*）
* `lxc info <container name>`的输出
* `lxc config show -expanded <container name>` 的输出
* `dmesg`（\*）的输出
* `/proc/self/mountinfo` 的输出（\*）
* `lxc exec <container name> - cat /proc/self/mountinfo` 的输出
* `uname -a`（\*）的输出
* `/var/log/lxd.log`（\*）的内容
* `/etc/default/lxd-bridge`（\*）的内容
* `/var/log/lxd/<container name>/` 的 tarball（\*）

如果报告迁移错误，而不是状态快照或有状态停止的错误，请将上面所有含有（\*）标记的源与目标主机的信息发来。

### 额外信息

CRIU 的网站在： <https://criu.org>

LXD 的主站在： <https://linuxcontainers.org/lxd>

LXD 的 GitHub 仓库： <https://github.com/lxc/lxd>

LXD 的邮件列表： <https://lists.linuxcontainers.org>

LXD 的 IRC 频道： #lxcontainers on irc.freenode.net

---

作者简介：我是 Stéphane Graber。我是 LXC 和 LXD 项目的领导者，目前在加拿大魁北克蒙特利尔的家所在的Canonical 有限公司担任 LXD 的技术主管。

---

via: <https://stgraber.org/2016/04/25/lxd-2-0-live-migration-912/>

作者：[Stéphane Graber](https://www.stgraber.org/author/stgraber/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 组织翻译，[Linux中国](https://linux.cn/) 荣誉推出


如何用 R 语言的 Shiny 库编写 web 程序

Summary: Shiny 是一个来自 RStudio 的工具包，它让创建 web 程序变得更容易。它能从 R 控制台轻松安装，只需要一行，就可以加载好最新的稳定版本来使用。

![](/data/attachment/album/201703/04/104753olrdy3rof336yw75.png)

我这个月在写一些更加长的文章，所以你们可以在几周后再来看看。本月，我想简要地提下我自己一直在玩的一个很棒的 R 库。

我的一个亲密朋友最近在用 R 编写东西。我一直都对它很感兴趣，也一直在试图挤时间，学习更多关于 R 的知识以及可用它做的事情。探索 R 的超强数字处理能力对我而言有些困难，因为我并不如我朋友那样有一个数学头脑。我进展有点慢，但我一直试图将它与我在其他领域的经验联系起来，我甚至开始考虑非常简单的 web 程序。

[Shiny](http://shiny.rstudio.com/) 是一个来自 RStudio 的工具包，它让创建 web 程序变得更容易。它能从 R 控制台轻松安装，只需要一行，就可以加载好最新的稳定版本来使用。这里有一个很棒的[教程](http://shiny.rstudio.com/tutorial)，它可以在前面课程基础上，带着你理解应用架设的概念。 Shiny 的授权是 GPLv3，源代码可以在 [GitHub](https://github.com/studio/shiny) 上获得。

这是一个用 Shiny 写的简单的小 web 程序：

```
library(shiny)

server <- function(input, output, session) {
    observe({
        myText <- paste("Value above is: ", input$textIn)
        updateTextInput(session, "textOut", value=myText)
    })
}

ui <- basicPage(
    h3("My very own sample application!"),
    textInput("textIn", "Input goes here, please."),
    textInput("textOut", "Results will be printed in this box")
)

shinyApp(ui = ui, server = server)

```

当你在输入框中输入文字时，它会被复制到输出框中提示语后。这并没有什么奇特的，但它向你展示了一个 Shiny 程序的基本结构。“server”部分允许你处理所有后端工作，如计算、数据库检索或程序需要发生的任何其他操作。“ui”部分定义了接口，它可以根据需要变得简单或复杂。

包括在 Shiny 中的 [Bootstrap](http://getbootstrap.com/) 有了大量样式和主题，所以在学习了一点后，就能用 R 创建大量功能丰富的 web 程序。使用附加包可以将功能扩展到更高级的 JavaScript 程序、模板等。

有几种方式处理 Shiny 的后端工作。如果你只是在本地运行你的程序，加载库就能做到。对于想要发布到网络上的程序，你可以在 [RStudio 的 Shiny 网站](http://shinyapps.io/)上共享它们，运行开源版本的 Shiny 服务器，或通过按年订阅服务从 RStudio 处购买 Shiny Server Pro。

经验丰富的 R 大牛可能已经知道 Shiny 了；它已经存在大约几年了。对于像我这样来自一个完全不同的编程语言，并且希望学习一点 R 的人来说，它是相当有帮助的。

---

作者简介：

D Ruth Bavousett - D Ruth Bavousett 作为一名系统管理员和软件开发人员已经很长时间了，她的专业生涯开始于 VAX 11/780。在她的职业生涯（迄今为止）中，她花费了大量的时间在满足库的需求上，她自 2008 年以来一直是 Koha 开源库自动化套件的贡献者. Ruth 目前在休斯敦的 cPanel 任 Perl 开发人员，他也作为首席员工效力于双猫公司。

---

via: <https://opensource.com/article/17/1/writing-new-web-apps-shiny>

作者：[D Ruth Bavousett](https://opensource.com/users/druthb) 译者：[geekpi](https://github.com/geekpi) 校对：[jasminepeng](https://github.com/jasminepeng)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


一个使用 asyncio 协程的网络爬虫（一）

look look small weiba

>
> 本文作者：
>
>
> A. Jesse Jiryu Davis 是纽约 MongoDB 的工程师。他编写了异步 MongoDB Python 驱动程序 Motor，也是 MongoDB C 驱动程序的开发领袖和 PyMongo 团队成员。 他也为 asyncio 和 Tornado 做了贡献，在 <http://emptysqua.re> 上写作。
>
>
> Guido van Rossum 是主流编程语言 Python 的创造者，Python 社区称他为 BDFL （<ruby> 仁慈的终生大独裁者 <rp>  （ </rp> <rt>  Benevolent Dictator For Life </rt> <rp>  ） </rp></ruby>）——这是一个来自 Monty Python 短剧的称号。他的主页是 [http://www.python.org/~guido/](http://www.python.org/%7Eguido/) 。
>
>
>

![](/data/attachment/album/201703/04/160052krxzwz2jepx15pxr.jpg)

### 介绍

经典的计算机科学强调高效的算法，尽可能快地完成计算。但是很多网络程序的时间并不是消耗在计算上，而是在等待许多慢速的连接或者低频事件的发生。这些程序暴露出一个新的挑战：如何高效的等待大量网络事件。一个现代的解决方案是异步 I/O。

这一章我们将实现一个简单的网络爬虫。这个爬虫只是一个原型式的异步应用，因为它等待许多响应而只做少量的计算。一次爬的网页越多，它就能越快的完成任务。如果它为每个动态的请求启动一个线程的话，随着并发请求数量的增加，它会在耗尽套接字之前，耗尽内存或者线程相关的资源。使用异步 I/O 可以避免这个的问题。

我们将分三个阶段展示这个例子。首先，我们会实现一个事件循环并用这个事件循环和回调来勾画出一只网络爬虫。它很有效，但是当把它扩展成更复杂的问题时，就会导致无法管理的混乱代码。然后，由于 Python 的协程不仅有效而且可扩展，我们将用 Python 的生成器函数实现一个简单的协程。在最后一个阶段，我们将使用 Python 标准库“asyncio”中功能完整的协程， 并通过异步队列完成这个网络爬虫。（在 [PyCon 2013](http://pyvideo.org/video/1667/keynote) 上，Guido 介绍了标准的 asyncio 库，当时称之为“Tulip”。）

### 任务

网络爬虫寻找并下载一个网站上的所有网页，也许还会把它们存档，为它们建立索引。从根 URL 开始，它获取每个网页，解析出没有遇到过的链接加到队列中。当网页没有未见到过的链接并且队列为空时，它便停止运行。

我们可以通过同时下载大量的网页来加快这一过程。当爬虫发现新的链接，它使用一个新的套接字并行的处理这个新链接，解析响应，添加新链接到队列。当并发很大时，可能会导致性能下降，所以我们会限制并发的数量，在队列保留那些未处理的链接，直到一些正在执行的任务完成。

### 传统方式

怎么使一个爬虫并发？传统的做法是创建一个线程池，每个线程使用一个套接字在一段时间内负责一个网页的下载。比如，下载 xkcd.com 网站的一个网页：

```
def fetch(url):
    sock = socket.socket()
    sock.connect(('xkcd.com', 80))
    request = 'GET {} HTTP/1.0\r\nHost: xkcd.com\r\n\r\n'.format(url)
    sock.send(request.encode('ascii'))
    response = b''
    chunk = sock.recv(4096)
    while chunk:
        response += chunk
        chunk = sock.recv(4096)

    # Page is now downloaded.
    links = parse_links(response)
    q.add(links)

```

套接字操作默认是阻塞的：当一个线程调用一个类似 `connect` 和 `recv` 方法时，它会阻塞，直到操作完成。（即使是 `send` 也能被阻塞，比如接收端在接受外发消息时缓慢而系统的外发数据缓存已经满了的情况下）因此，为了同一时间内下载多个网页，我们需要很多线程。一个复杂的应用会通过线程池保持空闲的线程来分摊创建线程的开销。同样的做法也适用于套接字，使用连接池。

到目前为止，使用线程的是成本昂贵的，操作系统对一个进程、一个用户、一台机器能使用线程做了不同的硬性限制。在 作者 Jesse 的系统中，一个 Python 线程需要 50K 的内存，开启上万个线程就会失败。每个线程的开销和系统的限制就是这种方式的瓶颈所在。

在 Dan Kegel 那一篇很有影响力的文章“[The C10K problem](http://www.kegel.com/c10k.html)”中，它提出了多线程方式在 I/O 并发上的局限性。他在开始写道，

>
> 网络服务器到了要同时处理成千上万的客户的时代了，你不这样认为么？毕竟，现在网络规模很大了。
>
>
>

Kegel 在 1999 年创造出“C10K”这个术语。一万个连接在今天看来还是可接受的，但是问题依然存在，只不过大小不同。回到那时候，对于 C10K 问题，每个连接启一个线程是不切实际的。现在这个限制已经成指数级增长。确实，我们的玩具网络爬虫使用线程也可以工作的很好。但是，对于有着千万级连接的大规模应用来说，限制依然存在：它会消耗掉所有线程，即使套接字还够用。那么我们该如何解决这个问题？

### 异步

异步 I/O 框架在一个线程中完成并发操作。让我们看看这是怎么做到的。

异步框架使用*非阻塞*套接字。异步爬虫中，我们在发起到服务器的连接前把套接字设为非阻塞：

```
sock = socket.socket()
sock.setblocking(False)
try:
    sock.connect(('xkcd.com', 80))
except BlockingIOError:
    pass

```

对一个非阻塞套接字调用 `connect` 方法会立即抛出异常，即使它可以正常工作。这个异常复现了底层 C 语言函数令人厌烦的行为，它把 `errno` 设置为 `EINPROGRESS`，告诉你操作已经开始。

现在我们的爬虫需要一种知道连接何时建立的方法，这样它才能发送 HTTP 请求。我们可以简单地使用循环来重试：

```
request = 'GET {} HTTP/1.0\r\nHost: xkcd.com\r\n\r\n'.format(url)
encoded = request.encode('ascii')

while True:
    try:
        sock.send(encoded)
        break  # Done.
    except OSError as e:
        pass

print('sent')

```

这种方法不仅消耗 CPU，也不能有效的等待*多个*套接字。在远古时代，BSD Unix 的解决方法是 `select`，这是一个 C 函数，它在一个或一组非阻塞套接字上等待事件发生。现在，互联网应用大量连接的需求，导致 `select` 被 `poll` 所代替，在 BSD 上的实现是 `kqueue` ，在 Linux 上是 `epoll`。它们的 API 和 `select` 相似，但在大数量的连接中也能有较好的性能。

Python 3.4 的 `DefaultSelector` 会使用你系统上最好的 `select` 类函数。要注册一个网络 I/O 事件的提醒，我们会创建一个非阻塞套接字，并使用默认 selector 注册它。

```
from selectors import DefaultSelector, EVENT_WRITE

selector = DefaultSelector()

sock = socket.socket()
sock.setblocking(False)
try:
    sock.connect(('xkcd.com', 80))
except BlockingIOError:
    pass

def connected():
    selector.unregister(sock.fileno())
    print('connected!')

selector.register(sock.fileno(), EVENT_WRITE, connected)

```

我们不理会这个伪造的错误，调用 `selector.register`，传递套接字文件描述符和一个表示我们想要监听什么事件的常量表达式。为了当连接建立时收到提醒，我们使用 `EVENT_WRITE` ：它表示什么时候这个套接字可写。我们还传递了一个 Python 函数 `connected`，当对应事件发生时被调用。这样的函数被称为*回调*。

在一个循环中，selector 接收到 I/O 提醒时我们处理它们。

```
def loop():
    while True:
        events = selector.select()
        for event_key, event_mask in events:
            callback = event_key.data
            callback()

```

`connected` 回调函数被保存在 `event_key.data` 中，一旦这个非阻塞套接字建立连接，它就会被取出来执行。

不像我们前面那个快速轮转的循环，这里的 `select` 调用会暂停，等待下一个 I/O 事件，接着执行等待这些事件的回调函数。没有完成的操作会保持挂起，直到进到下一个事件循环时执行。

到目前为止我们展现了什么？我们展示了如何开始一个 I/O 操作和当操作准备好时调用回调函数。异步*框架*，它在单线程中执行并发操作，其建立在两个功能之上，非阻塞套接字和事件循环。

我们这里达成了“<ruby> 并发性 <rp>  （ </rp> <rt>  concurrency </rt> <rp>  ） </rp></ruby>”，但不是传统意义上的“<ruby> 并行性 <rp>  （ </rp> <rt>  parallelism </rt> <rp>  ） </rp></ruby>”。也就是说，我们构建了一个可以进行重叠 I/O 的微小系统，它可以在其它操作还在进行的时候就开始一个新的操作。它实际上并没有利用多核来并行执行计算。这个系统是用于解决<ruby> I/O 密集 <rp>  （ </rp> <rt>  I/O-bound </rt> <rp>  ） </rp></ruby>问题的，而不是解决 <ruby> CPU 密集 <rp>  （ </rp> <rt>  CPU-bound </rt> <rp>  ） </rp></ruby>问题的。（Python 的全局解释器锁禁止在一个进程中以任何方式并行执行 Python 代码。在 Python 中并行化 CPU 密集的算法需要多个进程，或者以将该代码移植为 C 语言并行版本。但是这是另外一个话题了。）

所以，我们的事件循环在并发 I/O 上是有效的，因为它并不用为每个连接拨付线程资源。但是在我们开始前，我们需要澄清一个常见的误解：异步比多线程快。通常并不是这样的，事实上，在 Python 中，在处理少量非常活跃的连接时，像我们这样的事件循环是慢于多线程的。在运行时环境中是没有全局解释器锁的，在同样的负载下线程会执行的更好。异步 I/O 真正适用于事件很少、有许多缓慢或睡眠的连接的应用程序。（Jesse 在“[什么是异步，它如何工作，什么时候该用它？](http://pyvideo.org/video/2565/what-is-async-how-does-it-work-and-when-should)”一文中指出了异步所适用和不适用的场景。Mike Bayer 在“[异步 Python 和数据库](http://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/)”一文中比较了不同负载情况下异步 I/O 和多线程的不同。）

### 回调

用我们刚刚建立的异步框架，怎么才能完成一个网络爬虫？即使是一个简单的网页下载程序也是很难写的。

首先，我们有一个尚未获取的 URL 集合，和一个已经解析过的 URL 集合。

```
urls_todo = set(['/'])
seen_urls = set(['/'])

```

`seen_urls` 集合包括 `urls_todo` 和已经完成的 URL。用根 URL `/` 初始化它们。

获取一个网页需要一系列的回调。在套接字连接建立时会触发 `connected` 回调，它向服务器发送一个 GET 请求。但是它要等待响应，所以我们需要注册另一个回调函数；当该回调被调用，它仍然不能读取到完整的请求时，就会再一次注册回调，如此反复。

让我们把这些回调放在一个 `Fetcher` 对象中，它需要一个 URL，一个套接字，还需要一个地方保存返回的字节：

```
class Fetcher:
    def __init__(self, url):
        self.response = b''  # Empty array of bytes.
        self.url = url
        self.sock = None

```

我们的入口点在 `Fetcher.fetch`：

```
    # Method on Fetcher class.
    def fetch(self):
        self.sock = socket.socket()
        self.sock.setblocking(False)
        try:
            self.sock.connect(('xkcd.com', 80))
        except BlockingIOError:
            pass

        # Register next callback.
        selector.register(self.sock.fileno(),
                          EVENT_WRITE,
                          self.connected)

```

`fetch` 方法从连接一个套接字开始。但是要注意这个方法在连接建立前就返回了。它必须将控制返回到事件循环中等待连接建立。为了理解为什么要这样做，假设我们程序的整体结构如下：

```
# Begin fetching http://xkcd.com/353/
fetcher = Fetcher('/353/')
fetcher.fetch()

while True:
    events = selector.select()
    for event_key, event_mask in events:
        callback = event_key.data
        callback(event_key, event_mask)

```

当调用 `select` 函数后，所有的事件提醒才会在事件循环中处理，所以 `fetch` 必须把控制权交给事件循环，这样我们的程序才能知道什么时候连接已建立，接着循环调用 `connected` 回调，它已经在上面的 `fetch` 方法中注册过。

这里是我们的 `connected` 方法的实现：

```
    # Method on Fetcher class.
    def connected(self, key, mask):
        print('connected!')
        selector.unregister(key.fd)
        request = 'GET {} HTTP/1.0\r\nHost: xkcd.com\r\n\r\n'.format(self.url)
        self.sock.send(request.encode('ascii'))

        # Register the next callback.
        selector.register(key.fd,
                          EVENT_READ,
                          self.read_response)

```

这个方法发送一个 GET 请求。一个真正的应用会检查 `send` 的返回值，以防所有的信息没能一次发送出去。但是我们的请求很小，应用也不复杂。它只是简单的调用 `send`，然后等待响应。当然，它必须注册另一个回调并把控制权交给事件循环。接下来也是最后一个回调函数 `read_response`，它处理服务器的响应：

```
    # Method on Fetcher class.
    def read_response(self, key, mask):
        global stopped

        chunk = self.sock.recv(4096)  # 4k chunk size.
        if chunk:
            self.response += chunk
        else:
            selector.unregister(key.fd)  # Done reading.
            links = self.parse_links()

            # Python set-logic:
            for link in links.difference(seen_urls):
                urls_todo.add(link)
                Fetcher(link).fetch()  # <- New Fetcher.

            seen_urls.update(links)
            urls_todo.remove(self.url)
            if not urls_todo:
                stopped = True

```

这个回调在每次 `selector` 发现套接字*可读*时被调用，可读有两种情况：套接字接受到数据或它被关闭。

这个回调函数从套接字读取 4K 数据。如果不到 4k，那么有多少读多少。如果比 4K 多，`chunk` 中只包 4K 数据并且这个套接字保持可读，这样在事件循环的下一个周期，会再次回到这个回调函数。当响应完成时，服务器关闭这个套接字，`chunk` 为空。

这里没有展示的 `parse_links` 方法，它返回一个 URL 集合。我们为每个新的 URL 启动一个 fetcher。注意一个使用异步回调方式编程的好处：我们不需要为共享数据加锁，比如我们往 `seen_urls` 增加新链接时。这是一种非抢占式的多任务，它不会在我们代码中的任意一个地方被打断。

我们增加了一个全局变量 `stopped`，用它来控制这个循环：

```
stopped = False

def loop():
    while not stopped:
        events = selector.select()
        for event_key, event_mask in events:
            callback = event_key.data
            callback()

```

一旦所有的网页被下载下来，fetcher 停止这个事件循环，程序退出。

这个例子让异步编程的一个问题明显的暴露出来：意大利面代码。

我们需要某种方式来表达一系列的计算和 I/O 操作，并且能够调度多个这样的系列操作让它们并发的执行。但是，没有线程你不能把这一系列操作写在一个函数中：当函数开始一个 I/O 操作，它明确的把未来所需的状态保存下来，然后返回。你需要考虑如何写这个状态保存的代码。

让我们来解释下这到底是什么意思。先来看一下在线程中使用通常的阻塞套接字来获取一个网页时是多么简单。

```
# Blocking version.
def fetch(url):
    sock = socket.socket()
    sock.connect(('xkcd.com', 80))
    request = 'GET {} HTTP/1.0\r\nHost: xkcd.com\r\n\r\n'.format(url)
    sock.send(request.encode('ascii'))
    response = b''
    chunk = sock.recv(4096)
    while chunk:
        response += chunk
        chunk = sock.recv(4096)

    # Page is now downloaded.
    links = parse_links(response)
    q.add(links)

```

在一个套接字操作和下一个操作之间这个函数到底记住了什么状态？它有一个套接字，一个 URL 和一个可增长的 `response`。运行在线程中的函数使用编程语言的基本功能来在栈中的局部变量保存这些临时状态。这样的函数也有一个“continuation”——它会在 I/O 结束后执行这些代码。运行时环境通过线程的指令指针来记住这个 continuation。你不必考虑怎么在 I/O 操作后恢复局部变量和这个 continuation。语言本身的特性帮你解决。

但是用一个基于回调的异步框架时，这些语言特性不能提供一点帮助。当等待 I/O 操作时，一个函数必须明确的保存它的状态，因为它会在 I/O 操作完成之前返回并清除栈帧。在我们基于回调的例子中，作为局部变量的替代，我们把 `sock` 和 `response` 作为 Fetcher 实例 `self` 的属性来存储。而作为指令指针的替代，它通过注册 `connected` 和 `read_response` 回调来保存它的 continuation。随着应用功能的增长，我们需要手动保存的回调的复杂性也会增加。如此繁复的记账式工作会让编码者感到头痛。

更糟糕的是，当我们的回调函数抛出异常会发生什么？假设我们没有写好 `parse_links` 方法，它在解析 HTML 时抛出异常：

```
Traceback (most recent call last):
  File "loop-with-callbacks.py", line 111, in <module>
    loop()
  File "loop-with-callbacks.py", line 106, in loop
    callback(event_key, event_mask)
  File "loop-with-callbacks.py", line 51, in read_response
    links = self.parse_links()
  File "loop-with-callbacks.py", line 67, in parse_links
    raise Exception('parse error')
Exception: parse error

```

这个堆栈回溯只能显示出事件循环调用了一个回调。我们不知道是什么导致了这个错误。这条链的两边都被破坏：不知道从哪来也不知到哪去。这种丢失上下文的现象被称为“<ruby> 堆栈撕裂 <rp>  （ </rp> <rt>  stack ripping </rt> <rp>  ） </rp></ruby>”，经常会导致无法分析原因。它还会阻止我们为回调链设置异常处理，即那种用“try / except”块封装函数调用及其调用树。（对于这个问题的更复杂的解决方案，参见 <http://www.tornadoweb.org/en/stable/stack_context.html> ）

所以，除了关于多线程和异步哪个更高效的长期争议之外，还有一个关于这两者之间的争论：谁更容易跪了。如果在同步上出现失误，线程更容易出现数据竞争的问题，而回调因为"<ruby> 堆栈撕裂 <rp>  （ </rp> <rt>  stack ripping </rt> <rp>  ） </rp></ruby>"问题而非常难于调试。

（题图素材来自：[ruth-tay.deviantart.com](http://ruth-tay.deviantart.com/art/Bearded-Vulture-553800995)）

---

via: <http://aosabook.org/en/500L/pages/a-web-crawler-with-asyncio-coroutines.html>

作者：A. Jesse Jiryu Davis , Guido van Rossum 译者：[qingyunha](https://github.com/qingyunha) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译，[Linux中国](http://linux.cn/) 荣誉推出


一个使用 asyncio 协程的网络爬虫（二）

Summary: 然后，由于 Python 的协程不仅有效而且可扩展，我们将用 Python 的生成器函数实现一个简单的协程。

>
> 本文作者：
>
>
> A. Jesse Jiryu Davis 是纽约 MongoDB 的工程师。他编写了异步 MongoDB Python 驱动程序 Motor，也是 MongoDB C 驱动程序的开发领袖和 PyMongo 团队成员。 他也为 asyncio 和 Tornado 做了贡献，在 <http://emptysqua.re> 上写作。
>
>
> Guido van Rossum 是主流编程语言 Python 的创造者，Python 社区称他为 BDFL （仁慈的终生大独裁者 (Benevolent Dictator For Life)）——这是一个来自 Monty Python 短剧的称号。他的主页是 [http://www.python.org/~guido/](http://www.python.org/%7Eguido/) 。
>
>
>

###

### 协程

还记得我们对你许下的承诺么？我们可以写出这样的异步代码，它既有回调方式的高效，也有多线程代码的简洁。这个结合是同过一种称为<ruby> 协程 <rp>  （ </rp> <rt>  coroutine </rt> <rp>  ） </rp></ruby>的模式来实现的。使用 Python3.4 标准库 asyncio 和一个叫“aiohttp”的包，在协程中获取一个网页是非常直接的（ `@asyncio.coroutine` 修饰符并非魔法。事实上，如果它修饰的是一个生成器函数，并且没有设置 `PYTHONASYNCIODEBUG` 环境变量的话，这个修饰符基本上没啥用。它只是为了框架的其它部分方便，设置了一个属性 `_is_coroutine` 而已。也可以直接使用 asyncio 和裸生成器，而没有 `@asyncio.coroutine` 修饰符）：

```
    @asyncio.coroutine
    def fetch(self, url):
        response = yield from self.session.get(url)
        body = yield from response.read()

```

它也是可扩展的。在作者 Jesse 的系统上，与每个线程 50k 内存相比，一个 Python 协程只需要 3k 内存。Python 很容易就可以启动上千个协程。

协程的概念可以追溯到计算机科学的远古时代，它很简单，一个可以暂停和恢复的子过程。线程是被操作系统控制的抢占式多任务，而协程的多任务是可合作的，它们自己选择什么时候暂停去执行下一个协程。

有很多协程的实现。甚至在 Python 中也有几种。Python 3.4 标准库 asyncio 中的协程是建立在生成器之上的，这是一个 Future 类和“yield from”语句。从 Python 3.5 开始，协程变成了语言本身的特性（[“PEP 492 Coroutines with async and await syntax”](https://www.python.org/dev/peps/pep-0492/) 中描述了 Python 3.5 内置的协程）。然而，理解 Python 3.4 中这个通过语言原有功能实现的协程，是我们处理 Python 3.5 中原生协程的基础。

要解释 Python 3.4 中基于生成器的协程，我们需要深入生成器的方方面面，以及它们是如何在 asyncio 中用作协程的。我很高兴就此写点东西，想必你也希望继续读下去。我们解释了基于生成器的协程之后，就会在我们的异步网络爬虫中使用它们。

### 生成器如何工作

在你理解生成器之前，你需要知道普通的 Python 函数是怎么工作的。正常情况下，当一个函数调用一个子过程，这个被调用函数获得控制权，直到它返回或者有异常发生，才把控制权交给调用者：

```
>>> def foo():
...     bar()
...
>>> def bar():
...     pass

```

标准的 Python 解释器是用 C 语言写的。一个 Python 函数被调用所对应的 C 函数是 `PyEval_EvalFrameEx`。它获得一个 Python 栈帧结构并在这个栈帧的上下文中执行 Python 字节码。这里是 `foo` 函数的字节码：

```
>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (bar)
              3 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
              6 POP_TOP
              7 LOAD_CONST               0 (None)
             10 RETURN_VALUE

```

`foo` 函数在它栈中加载 `bar` 函数并调用它，然后把 `bar` 的返回值从栈中弹出，加载 `None` 值到堆栈并返回。

当 `PyEval_EvalFrameEx` 遇到 `CALL_FUNCTION` 字节码时，它会创建一个新的栈帧，并用这个栈帧递归的调用 `PyEval_EvalFrameEx` 来执行 `bar` 函数。

非常重要的一点是，Python 的栈帧在堆中分配！Python 解释器是一个标准的 C 程序，所以它的栈帧是正常的栈帧。但是 Python 的栈帧是在堆中处理。这意味着 Python 栈帧在函数调用结束后依然可以存在。我们在 `bar` 函数中保存当前的栈帧，交互式的看看这种现象：

```
>>> import inspect
>>> frame = None
>>> def foo():
...     bar()
...
>>> def bar():
...     global frame
...     frame = inspect.currentframe()
...
>>> foo()
>>> # The frame was executing the code for 'bar'.
>>> frame.f_code.co_name
'bar'
>>> # Its back pointer refers to the frame for 'foo'.
>>> caller_frame = frame.f_back
>>> caller_frame.f_code.co_name
'foo'

```

![Figure 5.1 - Function Calls](/data/attachment/album/201703/04/160300y153w1r22n9ilwb7.png)

现在该说 Python 生成器了，它使用同样构件——代码对象和栈帧——去完成一个不可思议的任务。

这是一个生成器函数：

```
>>> def gen_fn():
...     result = yield 1
...     print('result of yield: {}'.format(result))
...     result2 = yield 2
...     print('result of 2nd yield: {}'.format(result2))
...     return 'done'
...

```

在 Python 把 `gen_fn` 编译成字节码的过程中，一旦它看到 `yield` 语句就知道这是一个生成器函数而不是普通的函数。它就会设置一个标志来记住这个事实：

```
>>> # The generator flag is bit position 5.
>>> generator_bit = 1 << 5
>>> bool(gen_fn.__code__.co_flags & generator_bit)
True

```

当你调用一个生成器函数，Python 看到这个标志，就不会实际运行它而是创建一个生成器：

```
>>> gen = gen_fn()
>>> type(gen)
<class 'generator'>

```

Python 生成器封装了一个栈帧和函数体代码的引用：

```
>>> gen.gi_code.co_name
'gen_fn'

```

所有通过调用 `gen_fn` 的生成器指向同一段代码，但都有各自的栈帧。这些栈帧不再任何一个C函数栈中，而是在堆空间中等待被使用：

![Figure 5.2 - Generators](/data/attachment/album/201703/04/160301y30bcxoz0wwowdvc.png)

栈帧中有一个指向“最后执行指令”的指针。初始化为 -1，意味着它没开始运行：

```
>>> gen.gi_frame.f_lasti
-1

```

当我们调用 `send` 时，生成器一直运行到第一个 `yield` 语句处停止，并且 `send` 返回 1，因为这是 `gen` 传递给 `yield` 表达式的值。

```
>>> gen.send(None)
1

```

现在，生成器的指令指针是 3，所编译的Python 字节码一共有 56 个字节：

```
>>> gen.gi_frame.f_lasti
3
>>> len(gen.gi_code.co_code)
56

```

这个生成器可以在任何时候、任何函数中恢复运行，因为它的栈帧并不在真正的栈中，而是堆中。在调用链中它的位置也是不固定的，它不必遵循普通函数先进后出的顺序。它像云一样自由。

我们可以传递一个值 `hello` 给生成器，它会成为 `yield` 语句的结果，并且生成器会继续运行到第二个 `yield` 语句处。

```
>>> gen.send('hello')
result of yield: hello
2

```

现在栈帧中包含局部变量 `result`：

```
>>> gen.gi_frame.f_locals
{'result': 'hello'}

```

其它从 `gen_fn` 创建的生成器有着它自己的栈帧和局部变量。

当我们再一次调用 `send`，生成器继续从第二个 `yield` 开始运行，以抛出一个特殊的 `StopIteration` 异常为结束。

```
>>> gen.send('goodbye')
result of 2nd yield: goodbye
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration: done

```

这个异常有一个值 `"done"`，它就是生成器的返回值。

### 使用生成器构建协程

所以生成器可以暂停，可以给它一个值让它恢复，并且它还有一个返回值。这些特性看起来很适合去建立一个不使用那种乱糟糟的意面似的回调异步编程模型。我们想创造一个这样的“协程”：一个在程序中可以和其他过程合作调度的过程。我们的协程将会是标准库 `asyncio` 中协程的一个简化版本，我们将使用生成器，futures 和 `yield from` 语句。

首先，我们需要一种方法去代表协程所需要等待的 future 事件。一个简化的版本是：

```
class Future:
    def __init__(self):
        self.result = None
        self._callbacks = []

    def add_done_callback(self, fn):
        self._callbacks.append(fn)

    def set_result(self, result):
        self.result = result
        for fn in self._callbacks:
            fn(self)

```

一个 future 初始化为“未解决的”，它通过调用 `set_result` 来“解决”。（这个 future 缺少很多东西，比如说，当这个 future 解决后，<ruby> 生成 <rp>  （ </rp> <rt>  yield </rt> <rp>  ） </rp></ruby>的协程应该马上恢复而不是暂停，但是在我们的代码中却不没有这样做。参见 asyncio 的 Future 类以了解其完整实现。）

让我们用 future 和协程来改写我们的 fetcher。我们之前用回调写的 `fetch` 如下：

```
class Fetcher:
    def fetch(self):
        self.sock = socket.socket()
        self.sock.setblocking(False)
        try:
            self.sock.connect(('xkcd.com', 80))
        except BlockingIOError:
            pass
        selector.register(self.sock.fileno(),
                          EVENT_WRITE,
                          self.connected)

    def connected(self, key, mask):
        print('connected!')
        # And so on....

```

`fetch` 方法开始连接一个套接字，然后注册 `connected` 回调函数，它会在套接字建立连接后调用。现在我们使用协程把这两步合并：

```
    def fetch(self):
        sock = socket.socket()
        sock.setblocking(False)
        try:
            sock.connect(('xkcd.com', 80))
        except BlockingIOError:
            pass

        f = Future()

        def on_connected():
            f.set_result(None)

        selector.register(sock.fileno(),
                          EVENT_WRITE,
                          on_connected)
        yield f
        selector.unregister(sock.fileno())
        print('connected!')

```

现在，`fetch` 是一个生成器，因为它有一个 `yield` 语句。我们创建一个未决的 future，然后 yield 它，暂停 `fetch` 直到套接字连接建立。内联函数 `on_connected` 解决这个 future。

但是当 future 被解决，谁来恢复这个生成器？我们需要一个协程*驱动器*。让我们叫它 “task”:

```
class Task:
    def __init__(self, coro):
        self.coro = coro
        f = Future()
        f.set_result(None)
        self.step(f)

    def step(self, future):
        try:
            next_future = self.coro.send(future.result)
        except StopIteration:
            return

        next_future.add_done_callback(self.step)

# Begin fetching http://xkcd.com/353/
fetcher = Fetcher('/353/')
Task(fetcher.fetch())

loop()

```

task 通过传递一个 None 值给 `fetch` 来启动它。`fetch` 运行到它 yeild 出一个 future，这个 future 被作为 `next_future` 而捕获。当套接字连接建立，事件循环运行回调函数 `on_connected`，这里 future 被解决，`step` 被调用，`fetch` 恢复运行。

### 用 yield from 重构协程

一旦套接字连接建立，我们就可以发送 HTTP GET 请求，然后读取服务器响应。不再需要哪些分散在各处的回调函数，我们把它们放在同一个生成器函数中：

```
    def fetch(self):
        # ... connection logic from above, then:
        sock.send(request.encode('ascii'))

        while True:
            f = Future()

            def on_readable():
                f.set_result(sock.recv(4096))

            selector.register(sock.fileno(),
                              EVENT_READ,
                              on_readable)
            chunk = yield f
            selector.unregister(sock.fileno())
            if chunk:
                self.response += chunk
            else:
                # Done reading.
                break

```

从套接字中读取所有信息的代码看起来很通用。我们能不把它从 `fetch` 中提取成一个子过程？现在该 Python 3 热捧的 `yield from` 登场了。它能让一个生成器*委派*另一个生成器。

让我们先回到原来那个简单的生成器例子：

```
>>> def gen_fn():
...     result = yield 1
...     print('result of yield: {}'.format(result))
...     result2 = yield 2
...     print('result of 2nd yield: {}'.format(result2))
...     return 'done'
...

```

为了从其他生成器调用这个生成器，我们使用 `yield from` 委派它:

```
>>> # Generator function:
>>> def caller_fn():
...     gen = gen_fn()
...     rv = yield from gen
...     print('return value of yield-from: {}'
...           .format(rv))
...
>>> # Make a generator from the
>>> # generator function.
>>> caller = caller_fn()

```

这个 `caller` 生成器的行为的和它委派的生成器 `gen` 表现的完全一致：

```
>>> caller.send(None)
1
>>> caller.gi_frame.f_lasti
15
>>> caller.send('hello')
result of yield: hello
2
>>> caller.gi_frame.f_lasti  # Hasn't advanced.
15
>>> caller.send('goodbye')
result of 2nd yield: goodbye
return value of yield-from: done
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration

```

当 `caller` 自 `gen` 生成（`yield`），`caller` 就不再前进。注意到 `caller` 的指令指针保持15不变，就是 `yield from` 的地方，即使内部的生成器 `gen` 从一个 yield 语句运行到下一个 yield，它始终不变。（事实上，这就是“yield from”在 CPython 中工作的具体方式。函数会在执行每个语句之前提升其指令指针。但是在外部生成器执行“yield from”后，它会将其指令指针减一，以保持其固定在“yield form”语句上。然后其生成其 caller。这个循环不断重复，直到内部生成器抛出 StopIteration，这里指向外部生成器最终允许它自己进行到下一条指令的地方。）从 `caller` 外部来看，我们无法分辨 yield 出的值是来自 `caller` 还是它委派的生成器。而从 `gen` 内部来看，我们也不能分辨传给它的值是来自 `caller` 还是 `caller` 的外面。`yield from` 语句是一个光滑的管道，值通过它进出 `gen`，一直到 `gen` 结束。

协程可以用 `yield from` 把工作委派给子协程，并接收子协程的返回值。注意到上面的 `caller` 打印出“return value of yield-from: done”。当 `gen` 完成后，它的返回值成为 `caller` 中 `yield from` 语句的值。

```
    rv = yield from gen

```

前面我们批评过基于回调的异步编程模式，其中最大的不满是关于 “<ruby> 堆栈撕裂 <rp>  （ </rp> <rt>  stack ripping </rt> <rp>  ） </rp></ruby>”：当一个回调抛出异常，它的堆栈回溯通常是毫无用处的。它只显示出事件循环运行了它，而没有说为什么。那么协程怎么样？

```
>>> def gen_fn():
...     raise Exception('my error')
>>> caller = caller_fn()
>>> caller.send(None)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 3, in caller_fn
  File "<input>", line 2, in gen_fn
Exception: my error

```

这还是非常有用的，当异常抛出时，堆栈回溯显示出 `caller_fn` 委派了 `gen_fn`。令人更欣慰的是，你可以在一次异常处理器中封装这个调用到一个子过程中，像正常函数一样：

```
>>> def gen_fn():
...     yield 1
...     raise Exception('uh oh')
...
>>> def caller_fn():
...     try:
...         yield from gen_fn()
...     except Exception as exc:
...         print('caught {}'.format(exc))
...
>>> caller = caller_fn()
>>> caller.send(None)
1
>>> caller.send('hello')
caught uh oh

```

所以我们可以像提取子过程一样提取子协程。让我们从 fetcher 中提取一些有用的子协程。我们先写一个可以读一块数据的协程 `read`：

```
def read(sock):
    f = Future()

    def on_readable():
        f.set_result(sock.recv(4096))

    selector.register(sock.fileno(), EVENT_READ, on_readable)
    chunk = yield f  # Read one chunk.
    selector.unregister(sock.fileno())
    return chunk

```

在 `read` 的基础上，`read_all` 协程读取整个信息：

```
def read_all(sock):
    response = []
    # Read whole response.
    chunk = yield from read(sock)
    while chunk:
        response.append(chunk)
        chunk = yield from read(sock)

    return b''.join(response)

```

如果你换个角度看，抛开 `yield form` 语句的话，它们就像在做阻塞 I/O 的普通函数一样。但是事实上，`read` 和 `read_all` 都是协程。`yield from` `read` 暂停 `read_all` 直到 I/O 操作完成。当 `read_all` 暂停时，asyncio 的事件循环正在做其它的工作并等待其他的 I/O 操作。`read` 在下次循环中当事件就绪，完成 I/O 操作时，`read_all` 恢复运行。

最终，`fetch` 调用了 `read_all`：

```
class Fetcher:
    def fetch(self):
         # ... connection logic from above, then:
        sock.send(request.encode('ascii'))
        self.response = yield from read_all(sock)

```

神奇的是，Task 类不需要做任何改变，它像以前一样驱动外部的 `fetch` 协程：

```
Task(fetcher.fetch())
loop()

```

当 `read` yield 一个 future 时，task 从 `yield from` 管道中接收它，就像这个 future 直接从 `fetch` yield 一样。当循环解决一个 future 时，task 把它的结果送给 `fetch`，通过管道，`read` 接受到这个值，这完全就像 task 直接驱动 `read` 一样：

![Figure 5.3 - Yield From](/data/attachment/album/201703/04/160301bj00a92ax9pzxpdp.png)

为了完善我们的协程实现，我们再做点打磨：当等待一个 future 时，我们的代码使用 yield；而当委派一个子协程时，使用 yield from。不管是不是协程，我们总是使用 yield form 会更精炼一些。协程并不需要在意它在等待的东西是什么类型。

在 Python 中，我们从生成器和迭代器的高度相似中获得了好处，将生成器进化成 caller，迭代器也可以同样获得好处。所以，我们可以通过特殊的实现方式来迭代我们的 Future 类：

```
    # Method on Future class.
    def __iter__(self):
        # Tell Task to resume me here.
        yield self
        return self.result

```

future 的 `__iter__` 方法是一个 yield 它自身的一个协程。当我们将代码替换如下时：

```
# f is a Future.
yield f

```

以及……：

```
# f is a Future.
yield from f

```

……结果是一样的！驱动 Task 从它的调用 `send` 中接收 future，并当 future 解决后，它发回新的结果给该协程。

在每个地方都使用 `yield from` 的好处是什么？为什么比用 `field` 等待 future 并用 `yield from` 委派子协程更好？之所以更好的原因是，一个方法可以自由地改变其实行而不影响到其调用者：它可以是一个当 future 解决后返回一个值的普通方法，也可以是一个包含 `yield from` 语句并返回一个值的协程。无论是哪种情况，调用者仅需要 `yield from` 该方法以等待结果就行。

亲爱的读者，我们已经完成了对 asyncio 协程探索。我们深入观察了生成器的机制，实现了简单的 future 和 task。我们指出协程是如何利用两个世界的优点：比线程高效、比回调清晰的并发 I/O。当然真正的 asyncio 比我们这个简化版本要复杂的多。真正的框架需要处理zero-copy I/0、公平调度、异常处理和其他大量特性。

使用 asyncio 编写协程代码比你现在看到的要简单的多。在前面的代码中，我们从基本原理去实现协程，所以你看到了回调，task 和 future，甚至非阻塞套接字和 `select` 调用。但是当用 asyncio 编写应用，这些都不会出现在你的代码中。我们承诺过，你可以像这样下载一个网页：

```
    @asyncio.coroutine
    def fetch(self, url):
        response = yield from self.session.get(url)
        body = yield from response.read()

```

对我们的探索还满意么？回到我们原始的任务：使用 asyncio 写一个网络爬虫。

（题图素材来自：[ruth-tay.deviantart.com](http://ruth-tay.deviantart.com/art/Bearded-Vulture-553800995)）

---

via: <http://aosabook.org/en/500L/pages/a-web-crawler-with-asyncio-coroutines.html>

作者：A. Jesse Jiryu Davis , Guido van Rossum 译者：[qingyunha](https://github.com/qingyunha) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译，[Linux中国](http://linux.cn/) 荣誉推出