Summary: 如果你正在运行 Swarm 模式的集群，或者只运行单台 Docker，你都会有下面的疑问：我如何才能监控到它们都在干些什么？

您好，现在我们已经做到开始收集容器及应用日志，因为日志量过大现在还没有一个方案，您那边这两个日志收集是怎么做的

# 使用 Elasticsearch 和 cAdvisor 监控 Docker 容器

如果你正在运行 Swarm 模式的集群，或者只运行单台 Docker，你都会有下面的疑问：

>
> 我如何才能监控到它们都在干些什么？
>
>
>

这个问题的答案是“很不容易”。

你需要监控下面的参数：

1. 容器的数量和状态。
2. 一台容器是否已经移到另一个节点了，如果是，那是在什么时候，移动到哪个节点？
3. 给定节点上运行着的容器数量。
4. 一段时间内的通信峰值。
5. 孤儿卷和网络（LCTT 译注：孤儿卷就是当你删除容器时忘记删除它的卷，这个卷就不会再被使用，但会一直占用资源）。
6. 可用磁盘空间、可用 inode 数。
7. 容器数量与连接在 `docker0` 和 `docker_gwbridge` 上的虚拟网卡数量不一致（LCTT 译注：当 docker 启动时，它会在宿主机器上创建一个名为 docker0 的虚拟网络接口）。
8. 开启和关闭 Swarm 节点。
9. 收集并集中处理日志。

本文的目标是介绍 [Elasticsearch](https://github.com/elastic/elasticsearch) + [Kibana](https://github.com/elastic/kibana) + [cAdvisor](https://github.com/google/cadvisor) 的用法，使用它们来收集 Docker 容器的参数，分析数据并产生可视化报表。

阅读本文后你可以发现有一个监控仪表盘能够部分解决上述列出的问题。但如果只是使用 cAdvisor，有些参数就无法显示出来，比如 Swarm 模式的节点。

如果你有一些 cAdvisor 或其他工具无法解决的特殊需求，我建议你开发自己的数据收集器和数据处理器（比如 [Beats](https://github.com/elastic/beats)），请注意我不会演示如何使用 Elasticsearch 来集中收集 Docker 容器的日志。

>
> [“你要如何才能监控到 Swarm 模式集群里面发生了什么事情？要做到这点很不容易。” —— @fntlnz](https://twitter.com/share?text=%22How+do+you+keep+track+of+all+that%27s+happening+in+a+Swarm+Mode+cluster%3F+Not+easily.%22+via+%40fntlnz&url=https://blog.codeship.com/monitoring-docker-containers-with-elasticsearch-and-cadvisor/)
>
>
>

### 我们为什么要监控容器？

想象一下这个经典场景：你在管理一台或多台虚拟机，你把 tmux 工具用得很溜，用各种 session 事先设定好了所有基础的东西，包括监控。然后生产环境出问题了，你使用 `top`、`htop`、`iotop`、`jnettop` 各种 top 来排查，然后你准备好修复故障。

现在重新想象一下你有 3 个节点，包含 50 台容器，你需要在一个地方查看整洁的历史数据，这样你知道问题出在哪个地方，而不是把你的生命浪费在那些字符界面来赌你可以找到问题点。

### 什么是 Elastic Stack ？

Elastic Stack 就一个工具集，包括以下工具：

* Elasticsearch
* Kibana
* Logstash
* Beats

我们会使用其中一部分工具，比如使用 Elasticsearch 来分析基于 JSON 格式的文本，以及使用 Kibana 来可视化数据并产生报表。

另一个重要的工具是 [Beats](https://github.com/elastic/beats)，但在本文中我们还是把精力放在容器上，官方的 Beats 工具不支持 Docker，所以我们选择原生兼容 Elasticsearch 的 cAdvisor。

[cAdvisor](https://github.com/google/cadvisor) 工具负责收集、整合正在运行的容器数据，并导出报表。在本文中，这些报表被到入到 Elasticsearch 中。

cAdvisor 有两个比较酷的特性：

* 它不只局限于 Docker 容器。
* 它有自己的 Web 服务器，可以简单地显示当前节点的可视化报表。

### 设置测试集群，或搭建自己的基础架构

和我[以前的文章](https://blog.codeship.com/nginx-reverse-proxy-docker-swarm-clusters/)一样，我习惯提供一个简单的脚本，让读者不用花很多时间就能部署好和我一样的测试环境。你可以使用以下（非生产环境使用的）脚本来搭建一个 Swarm 模式的集群，其中一个容器运行着 Elasticsearch。

>
> 如果你有充足的时间和经验，你可以<ruby> 搭建自己的基础架构 <rp>  （ </rp> <rt>  Bring Your Own Infrastructure，BYOI </rt> <rp>  ） </rp></ruby>。
>
>
>

如果要继续阅读本文，你需要：

* 运行 Docker 进程的一个或多个节点（docker 版本号大于等于 1.12）。
* 至少有一个独立运行的 Elasticsearch 节点（版本号 2.4.X）。

重申一下，此 Elasticsearch 集群环境不能放在生产环境中使用。生产环境也不推荐使用单节点集群，所以如果你计划安装一个生产环境，请参考 [Elastic 指南](https://www.elastic.co/guide/en/elasticsearch/guide/2.x/deploy.html)。

### 对喜欢尝鲜的用户的友情提示

我就是一个喜欢尝鲜的人（当然我也已经在生产环境中使用了最新的 alpha 版本），但是在本文中，我不会使用最新的 Elasticsearch 5.0.0 alpha 版本，我还不是很清楚这个版本的功能，所以我不想成为那个引导你们出错的关键。

所以本文中涉及的 Elasticsearch 版本为最新稳定版 2.4.0。

### 测试集群部署脚本

前面已经说过，我提供这个脚本给你们，让你们不必费神去部署 Swarm 集群和 Elasticsearch，当然你也可以跳过这一步，用你自己的 Swarm 模式引擎和你自己的 Elasticserch 节点。

执行这段脚本之前，你需要：

* [Docker Machine](https://docs.docker.com/machine/install-machine/) – 最终版：在 DigitalOcean 中提供 Docker 引擎。
* [DigitalOcean API Token](https://cloud.digitalocean.com/settings/api/tokens/new): 让 docker 机器按照你的意思来启动节点。

### 创建集群的脚本

现在万事俱备，你可以把下面的代码拷到 create-cluster.sh 文件中：

```
#!/usr/bin/env bash
#
# Create a Swarm Mode cluster with a single master and a configurable number of workers

workers=${WORKERS:-"worker1 worker2"}

#######################################
# Creates a machine on Digital Ocean
# Globals:
#   DO_ACCESS_TOKEN The token needed to access DigitalOcean's API
# Arguments:
#   $1 the actual name to give to the machine
#######################################
create_machine() {
  docker-machine create \
    -d digitalocean \
    --digitalocean-access-token=$DO_ACCESS_TOKEN \
    --digitalocean-size 2gb \
    $1
}

#######################################
# Executes a command on the specified machine
# Arguments:
#   $1     The machine on which to run the command
#   $2..$n The command to execute on that machine
#######################################
machine_do() {
  docker-machine ssh $@
}

main() {

  if [ -z "$DO_ACCESS_TOKEN" ]; then
    echo "Please export a DigitalOcean Access token: https://cloud.digitalocean.com/settings/api/tokens/new"
    echo "export DO_ACCESS_TOKEN=<yourtokenhere>"
    exit 1
  fi

  if [ -z "$WORKERS" ]; then
    echo "You haven't provided your workers by setting the \$WORKERS environment variable, using the default ones: $workers"
  fi

  # Create the first and only master
  echo "Creating the master"

  create_machine master1

  master_ip=$(docker-machine ip master1)

  # Initialize the swarm mode on it
  echo "Initializing the swarm mode"
  machine_do master1 docker swarm init --advertise-addr $master_ip

  # Obtain the token to allow workers to join
  worker_tkn=$(machine_do master1 docker swarm join-token -q worker)
  echo "Worker token: ${worker_tkn}"

  # Create and join the workers
  for worker in $workers; do
    echo "Creating worker ${worker}"
    create_machine $worker
    machine_do $worker docker swarm join --token $worker_tkn $master_ip:2377
  done
}

main $@

```

赋予它可执行权限：

```
chmod +x create-cluster.sh

```

### 创建集群

如文件名所示，我们可以用它来创建集群。默认情况下这个脚本会创建一个 master 和两个 worker，如果你想修改 worker 个数，可以设置环境变量 WORKERS。

现在就来创建集群吧。

```
./create-cluster.sh

```

你可以出去喝杯咖啡，因为这需要花点时间。

最后集群部署好了。

现在为了验证 Swarm 模式集群已经正常运行，我们可以通过 ssh 登录进 master：

```
docker-machine ssh master1

```

然后列出集群的节点：

```
docker node ls

```

```
ID                           HOSTNAME  STATUS  AVAILABILITY  MANAGER STATUS
26fi3wiqr8lsidkjy69k031w2 *  master1   Ready   Active        Leader
dyluxpq8sztj7kmwlzs51u4id    worker2   Ready   Active
epglndegvixag0jztarn2lte8    worker1   Ready   Active

```

### 安装 Elasticsearch 和 Kibana

>
> 注意，从现在开始所有的命令都运行在主节点 master1 上。
>
>
>

在生产环境中，你可能会把 Elasticsearch 和 Kibana 安装在一个单独的、[大小合适](https://www.elastic.co/blog/found-sizing-elasticsearch)的实例集合中。但是在我们的实验中，我们还是把它们和 Swarm 模式集群安装在一起。

为了将 Elasticsearch 和 cAdvisor 连通，我们需要创建一个自定义的网络，因为我们使用了集群，并且容器可能会分布在不同的节点上，我们需要使用 [overlay](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) 网络（LCTT 译注：overlay 网络是指在不改变现有网络基础设施的前提下，通过某种约定通信协议，把二层报文封装在 IP 报文之上的新的数据格式，是目前最主流的容器跨节点数据传输和路由方案）。

也许你会问，“为什么还要网络？我们不是可以用 link 吗？” 请考虑一下，自从引入*用户定义网络*后，link 机制就已经过时了。

以下内容摘自 [Docker 文档](https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/)：

>
> 在 Docker network 特性出来以前，你可以使用 Docker link 特性实现容器互相发现、安全通信。而在 network 特性出来以后，你还可以使用 link，但是当容器处于默认桥接网络或用户自定义网络时，它们的表现是不一样的。
>
>
>

现在创建 overlay 网络，名称为 monitoring：

```
docker network create monitoring -d overlay

```

### Elasticsearch 容器

```
docker service create --network=monitoring \
  --mount type=volume,target=/usr/share/elasticsearch/data \
  --constraint node.hostname==worker1 \
  --name elasticsearch elasticsearch:2.4.0

```

注意 Elasticsearch 容器被限定在 worker1 节点，这是因为它运行时需要依赖 worker1 节点上挂载的卷。

### Kibana 容器

```
docker service create --network=monitoring --name kibana -e ELASTICSEARCH_URL="http://elasticsearch:9200" -p 5601:5601 kibana:4.6.0

```

如你所见，我们启动这两个容器时，都让它们加入 monitoring 网络，这样一来它们可以通过名称（如 Kibana）被相同网络的其他服务访问。

现在，通过 [routing mesh](https://docs.docker.com/engine/swarm/ingress/) 机制，我们可以使用浏览器访问服务器的 IP 地址来查看 Kibana 报表界面。

获取 master1 实例的公共 IP 地址：

```
docker-machine ip master1

```

打开浏览器输入地址：`http://[master1 的 ip 地址]:5601/status`

所有项目都应该是绿色：

让我们接下来开始收集数据！

### 收集容器的运行数据

收集数据之前，我们需要创建一个服务，以全局模式运行 cAdvisor，为每个有效节点设置一个定时任务。

这个服务与 Elasticsearch 处于相同的网络，以便于 cAdvisor 可以推送数据给 Elasticsearch。

```
docker service create --network=monitoring --mode global --name cadvisor \
  --mount type=bind,source=/,target=/rootfs,readonly=true \
  --mount type=bind,source=/var/run,target=/var/run,readonly=false \
  --mount type=bind,source=/sys,target=/sys,readonly=true \
  --mount type=bind,source=/var/lib/docker/,target=/var/lib/docker,readonly=true \
  google/cadvisor:latest \
  -storage_driver=elasticsearch \
  -storage_driver_es_host="http://elasticsearch:9200"

```

>
> 注意：如果你想配置 cAdvisor 选项，参考[这里](https://github.com/google/cadvisor/blob/master/docs/runtime_options.md)。
>
>
>

现在 cAdvisor 在发送数据给 Elasticsearch，我们通过定义一个索引模型来检索 Kibana 中的数据。有两种方式可以做到这一点：通过 Kibana 或者通过 API。在这里我们使用 API 方式实现。

我们需要在一个连接到 monitoring 网络的正在运行的容器中运行索引创建命令，你可以在 cAdvisor 容器中拿到 shell，不幸的是 Swarm 模式在开启服务时会在容器名称后面附加一个唯一的 ID 号，所以你需要手动指定 cAdvisor 容器的名称。

拿到 shell：

```
docker exec -ti <cadvisor-container-name> sh

```

创建索引：

```
curl -XPUT http://elasticsearch:9200/.kibana/index-pattern/cadvisor -d '{"title" : "cadvisor*",  "timeFieldName": "container_stats.timestamp"}'

```

如果你够懒，可以只执行下面这一句：

```
docker exec $(docker ps | grep cadvisor | awk '{print $1}' | head -1) curl -XPUT http://elasticsearch:9200/.kibana/index-pattern/cadvisor -d '{"title" : "cadvisor*",  "timeFieldName": "container_stats.timestamp"}'

```

### 把数据汇总成报表

你现在可以使用 Kibana 来创建一份美观的报表了。但是不要着急，我为你们建了一份报表和一些图形界面来方便你们入门。

访问 Kibana 界面 => Setting => Objects => Import，然后选择包含以下内容的 JSON 文件，就可以导入我的配置信息了：

```
[
  {
    "_id": "cAdvisor",
    "_type": "dashboard",
    "_source": {
      "title": "cAdvisor",
      "hits": 0,
      "description": "",
      "panelsJSON": "[{\"id\":\"Filesystem-usage\",\"type\":\"visualization\",\"panelIndex\":1,\"size_x\":6,\"size_y\":3,\"col\":1,\"row\":1},{\"id\":\"Memory-[Node-equal->Container]\",\"type\":\"visualization\",\"panelIndex\":2,\"size_x\":6,\"size_y\":4,\"col\":7,\"row\":4},{\"id\":\"memory-usage-by-machine\",\"type\":\"visualization\",\"panelIndex\":3,\"size_x\":6,\"size_y\":6,\"col\":1,\"row\":4},{\"id\":\"CPU-Total-Usage\",\"type\":\"visualization\",\"panelIndex\":4,\"size_x\":6,\"size_y\":5,\"col\":7,\"row\":8},{\"id\":\"Network-RX-TX\",\"type\":\"visualization\",\"panelIndex\":5,\"size_x\":6,\"size_y\":3,\"col\":7,\"row\":1}]",
      "optionsJSON": "{\"darkTheme\":false}",
      "uiStateJSON": "{}",
      "version": 1,
      "timeRestore": false,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"filter\":[{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}}}]}"
      }
    }
  },
  {
    "_id": "Network",
    "_type": "search",
    "_source": {
      "title": "Network",
      "description": "",
      "hits": 0,
      "columns": [
        "machine_name",
        "container_Name",
        "container_stats.network.name",
        "container_stats.network.interfaces",
        "container_stats.network.rx_bytes",
        "container_stats.network.rx_packets",
        "container_stats.network.rx_dropped",
        "container_stats.network.rx_errors",
        "container_stats.network.tx_packets",
        "container_stats.network.tx_bytes",
        "container_stats.network.tx_dropped",
        "container_stats.network.tx_errors"
      ],
      "sort": [
        "container_stats.timestamp",
        "desc"
      ],
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"cadvisor*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"highlight\":{\"pre_tags\":[\"@kibana-highlighted-field@\"],\"post_tags\":[\"@/kibana-highlighted-field@\"],\"fields\":{\"*\":{}},\"fragment_size\":2147483647},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "Filesystem-usage",
    "_type": "visualization",
    "_source": {
      "title": "Filesystem usage",
      "visState": "{\"title\":\"Filesystem usage\",\"type\":\"histogram\",\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"defaultYExtents\":false,\"mode\":\"stacked\",\"scale\":\"linear\",\"setYExtents\":false,\"shareYAxis\":true,\"times\":[],\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.filesystem.usage\",\"customLabel\":\"USED\"}},{\"id\":\"2\",\"type\":\"terms\",\"schema\":\"split\",\"params\":{\"field\":\"machine_name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"row\":false}},{\"id\":\"3\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.filesystem.capacity\",\"customLabel\":\"AVAIL\"}},{\"id\":\"4\",\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"container_stats.filesystem.device\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
      "uiStateJSON": "{\"vis\":{\"colors\":{\"Average container_stats.filesystem.available\":\"#E24D42\",\"Average container_stats.filesystem.base_usage\":\"#890F02\",\"Average container_stats.filesystem.capacity\":\"#3F6833\",\"Average container_stats.filesystem.usage\":\"#E24D42\",\"USED\":\"#BF1B00\",\"AVAIL\":\"#508642\"}}}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"cadvisor*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "CPU-Total-Usage",
    "_type": "visualization",
    "_source": {
      "title": "CPU Total Usage",
      "visState": "{\"title\":\"CPU Total Usage\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.cpu.usage.total\"}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"container_stats.timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"container_Name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}},{\"id\":\"4\",\"type\":\"terms\",\"schema\":\"split\",\"params\":{\"field\":\"machine_name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"row\":true}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"cadvisor*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "memory-usage-by-machine",
    "_type": "visualization",
    "_source": {
      "title": "Memory [Node]",
      "visState": "{\"title\":\"Memory [Node]\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.memory.usage\"}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"container_stats.timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"machine_name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"cadvisor*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "Network-RX-TX",
    "_type": "visualization",
    "_source": {
      "title": "Network RX TX",
      "visState": "{\"title\":\"Network RX TX\",\"type\":\"histogram\",\"params\":{\"addLegend\":true,\"addTimeMarker\":true,\"addTooltip\":true,\"defaultYExtents\":false,\"mode\":\"stacked\",\"scale\":\"linear\",\"setYExtents\":false,\"shareYAxis\":true,\"times\":[],\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.network.rx_bytes\",\"customLabel\":\"RX\"}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"container_stats.timestamp\",\"interval\":\"s\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.network.tx_bytes\",\"customLabel\":\"TX\"}}],\"listeners\":{}}",
      "uiStateJSON": "{\"vis\":{\"colors\":{\"RX\":\"#EAB839\",\"TX\":\"#BF1B00\"}}}",
      "description": "",
      "savedSearchId": "Network",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"filter\":[]}"
      }
    }
  },
  {
    "_id": "Memory-[Node-equal->Container]",
    "_type": "visualization",
    "_source": {
      "title": "Memory [Node=>Container]",
      "visState": "{\"title\":\"Memory [Node=>Container]\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.memory.usage\"}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"container_stats.timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"container_Name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}},{\"id\":\"4\",\"type\":\"terms\",\"schema\":\"split\",\"params\":{\"field\":\"machine_name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"row\":true}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"cadvisor*\",\"query\":{\"query_string\":{\"query\":\"* NOT container_Name.raw: \\\\\\\"/\\\\\\\" AND NOT container_Name.raw: \\\\\\\"/docker\\\\\\\"\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  }
]

```

这里还有很多东西可以玩，你也许想自定义报表界面，比如添加内存页错误状态，或者收发包的丢包数。如果你能实现开头列表处我没能实现的项目，那也是很好的。

### 总结

正确监控需要大量时间和精力，容器的 CPU、内存、IO、网络和磁盘，监控的这些参数还只是整个监控项目中的沧海一粟而已。

我不知道你做到了哪一阶段，但接下来的任务也许是：

* 收集运行中的容器的日志
* 收集应用的日志
* 监控应用的性能
* 报警
* 监控健康状态

如果你有意见或建议，请留言。祝你玩得开心。

现在你可以关掉这些测试系统了：

```
docker-machine rm master1 worker{1,2}

```

---

via: <https://blog.codeship.com/monitoring-docker-containers-with-elasticsearch-and-cadvisor/>

作者：[Lorenzo Fontana](https://blog.codeship.com/author/lorenzofontana/) 译者：[bazz2](https://github.com/bazz2) 校对：[wxy](https://github.com/wxy)

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


KDE 庆祝 20 岁生日，发布了首个 LTS 版本： KDE 5.8

good

就等KDE5毕业了

archlinux中的plasma 5 的breeze主题还是不太完善，对gtk的支持不够好。

KDE 5.8 还在 Testing 里

默认的 Fedora24 KDE 版本用起来简直就是灾难，忘了版本号是多少了，如果中文化跟稳定性完美，那还能接受。

非常好用

英文显示非常清晰好看，中文就模模糊糊，不知道中文渲染怎么回事

# KDE 庆祝 20 岁生日，发布了首个 LTS 版本： KDE 5.8

10 月 4 日，KDE 项目发布了其最新的桌面环境 KDE 5.8，它是面向 GNU/Linux 发行版的流行的现代桌面环境。

经过几个月的开发和几个版本的迭代，KDE 5.8 带着若干新功能闪亮登场，取代了之前的 5.7 系列——虽然 5.7 才于 9 月 13 日发布了其第五个维护版本。KDE 5.8 是一个长期支持版本（LTS），它会一直支持到 2018 年。

“**今天，KDE 发布了其旗舰产品 Plasma 的首个 LTS 版本**”，[公告](https://www.kde.org/announcements/plasma-5.8.0.php)中称，“这标志着它的开发人员和设计师们认为它已经可以推荐给普罗大众了。如果你以前体验过 KDE，但是由于种种原因弃之而去，那么现在是你该重新考虑回来的时候了。Plasma 是一个初看简单，实则强大的桌面环境！”

*（注，虽然 KDE 3.5 发布于 2005 年 11 月 29 日，而下一个版本 4.0 在 2008 年 1 月 11 日才发布，但是 KDE Plasma 5.8 是官方认可的第一个 LTS 版本）*

### KDE 主要功能

新版本的 KDE 的主要功能包括：支持桌面小工具；新的系统级搜索功能可以让用户在 KDE 桌面里面搜索任何东西，包括应用、音乐、视频、文件、目录等等；此外还有很多新的东西，包括桌面墙纸、小工具、桌面效果、窗口风格，以及丰富的定制能力。

#### 桌面小工具

*桌面小工具*

放在你桌面上的有用小工具可以让你及时关注每天的天气，休息的时候可以欣赏漫画，或者简单的用来做做计算。

#### 各种新鲜热辣的装饰素材

*各种新鲜热辣的装饰素材*

用下载的墙纸、窗口风格、小工具、桌面效果和大量的其它装饰素材打扮你的桌面。我们提供了一个 [KDE 商店](http://store.kde.org/)，可供你在海量的素材中进行选择。

#### 桌面搜索

*桌面搜索*

Plasma 可以让你在桌面上搜索应用、文件夹、音乐、视频、文件等等你任何想找的东西。

#### 统一的外观

*统一的外观*

Plasma 的默认 Breeze 主题将各种可视组件的外观打造为一致的风格，这包括用 Qt 4、Qt 5、GTK 2、GTK 3 等等开发的应用，甚至还包括 LibreOffice。

#### 与手机集成

*与手机集成*

使用 KDE Connect 你可以在桌面上接到短信提醒、可以轻松传输文件、在接到电话时自动静默音乐，甚至将你的手机当成一个遥控器。

#### 无尽的定制可能性

*无尽的定制可能性*

Plasma 以简洁为本，但是你可以用各种小工具、面板、屏幕和风格来定制它。

### KDE 5.8 中的新变化

KDE 完美打造了从开机到关机的整个过程、支持从右向左文字、简化了全局快捷键、改进了许多小应用、修复了多屏支持的问题、更好的支持了坞站和投影仪、增强了 KWin 窗口管理器、新的默认字体、Kirigami 界面更舒适，以及更好的 Wayland 支持！

#### 从启动到关机的一致体验

*从启动到关机的一致体验*

这个版本带来了全新的登录屏设计，从开机到关闭，给你一个完整的 Breeze 体验。布局经过精细打磨，更适合作为团体或公司办公用的计算机使用。它更加顺滑，也支持更多的定制能力，比如说，所有的 Plasma 插件，比如幻灯片小工具或动画墙纸小工具现在都可以放在锁屏上了。

#### 支持从右向左的语言

*支持从右向左的语言*

对希伯来语和阿拉伯语这样从右向左书写的语言的支持进行了极大改进。面板内容、桌面和配置对话框会如镜面般反射过来。Plasma 的边栏，比如小工具栏、窗口切换器、活动管理群都放在了屏幕右侧。

#### 改进的应用

*媒体播放器的上下文菜单*

重写了虚拟桌面切换器（即 Pager）和窗口列表应用，使用了我们在 5.7 中引入的新任务管理器后端，这可以让它们使用同任务管理器一样的数据，从而降低了内存占用并提升了性能。在多屏幕时，虚拟桌面切换器现在也可以设置为仅显示在当前屏幕上，现在它与活动切换器应用大部分代码都相同。

任务管理器在这个版本中有了更多的用处。以前仅在任务管理器提示菜单中可用的媒体控制器现在也可以在上下文菜单中访问了。此外，在拖放操作中可以将窗口拖到前端，将文件拖入任务管理器中会以其关联的应用打开。分组窗口的弹窗现在可以使用键盘进行导航，并且其标签的文字渲染也进行了改进。

#### 简化的全局快捷键

*全局快捷键设置*

全局快捷键的配置现在针对应用启动后最常用的任务进行了简化。跳转功能是在前一个版本中增加的，全局快捷键现在可以配置跳转到一个应用内的特定功能上。

由于我们在 Wayland 方面的突破，现在可以提供称之为“<ruby> 修饰快捷键 <rp>  （ </rp> <rt>  modifier-only shortcuts </rt> <rp>  ） </rp></ruby>”的功能，它可以让你只需要按下 Alt 键（或 Meta 键）就可以打开应用菜单。由于这个功能很受欢迎，所以被移植回 X11 会话中了。

#### 其他改进

*Plasma Discover 的新界面*

这个版本修复了多屏支持方面许多问题，并在 Qt 5.6.1 的支持下显著提升了坞站和投影仪的体验。

Plasma 的窗口管理器 KWin 现在通过 llvmpipe 进行<ruby> 合成 <rp>  （ </rp> <rt>  compositing </rt> <rp>  ） </rp></ruby>，更易于用在少见的硬件和嵌入式设备上。对于要求关闭合成的应用现在有了标准化而广泛使用的界面，移除了“<ruby> 非重定向全屏 <rp>  （ </rp> <rt>  Unredirect Fullscreen </rt> <rp>  ） </rp></ruby>”选项，它通常会导致一些稳定性问题，因为很多驱动已经不支持它了。

我们的跨平台 UI 组件 [Kirigami](https://dot.kde.org/2016/08/10/kdes-kirigami-ui-framework-gets-its-first-public-release) 现在已经发布，你可以试试看我们用它重新打造的 Plasma Discover。

我们也换了新的默认字体，来自 Google 的 Noto 字体包括了 Unicode 标准中的所有字符，而我们的等宽字体修饰非常适合于程序员和面向终端的用户。

#### Wayland 支持

*Wayland 之上的 Plasma 现在支持 GTK+*

在过去几个月当中，在 Wayland 上运行 Plasma 经历了漫长的工作。虽然我们的长期支持的范围并不包括快速发展的 Wayland 方面，不过我们认为它可以经受更多的测试了。虽然仍然有一些小问题和功能缺失，但是现在我们希望你可以试试它并给我们反馈。这个版本中部分改进如下：

* 支持 xdg-shell，比如现在支持 GTK+ 应用了
* 对触屏支持做了很多改进
* 支持触屏手势，虽然现在还没有任何手势
* 现在支持滑动弹窗效果
* 在 X11 和 Wayland 应用之间同步剪贴板内容

更多的详细变更内容请参见[变更日志](https://www.kde.org/announcements/plasma-5.7.5-5.8.0-changelog.php)。

### 支持期直到 2018 年 4 月

如上面所提到的，KDE Plasma 5.8 LTS 会支持到 2018 年，并将会发布至少 9 个维护版本。而第一个维护版本 KDE Plasma 5.8.1 在下周的 10 月 11 日就要发布了！按照[开发计划](https://community.kde.org/Schedules/Plasma_5)，开始的维护版本发布会很频繁，简直让人目不暇接：之后再过一周会发布 5.8.2，11 月 1 日发布 5.8.3，11 月 22 日发布 5.8.4。

### 下载与使用

#### 体验镜像

体验 KDE 5.8 最容易的方式是将体验镜像放到可启动的 USB 棒中，你可用在 KDE 社区维基中找到 [Plasma 5 的体验镜像列表](https://community.kde.org/Plasma/LiveImages)。

#### 下载软件包

软件包如下：

* [软件包下载](https://community.kde.org/Plasma/Packages)

#### 源代码

你也可以从源代码直接安装 Plasma 5。KDE 社区维基有如何[编译的指导](http://community.kde.org/Frameworks/Building)。注意，Plasma 5 不能与 Plasma 4 共存，你需要卸载旧版本或将其安装到不同的前缀目录下。

* [源代码](https://www.kde.org/info/plasma-5.8.0.php)

### KDE 20 周年纪念北京庆祝沙龙

2016 年 10 月 15 日，由普华基础软件与“Linux 中国”开源社区共同联合主办的 [KDE 20 周年庆祝沙龙在北京召开](http://s.wcd.im/v/158vrZ3a/)，欢迎各位 KDE 用户和开源爱好者们参加，沙龙不但由社区骨灰级爱好者进行讲演，而且还提供了琳琅满目的独有纪念品。报名人数名额限定，希望参加者需尽快[报名](http://s.wcd.im/v/158vrZ3a/)。

这是 KDE 在国内的第一次大型活动。该活动得到了 KDE 的官方认证，可以通过[官方活动入口](https://community.kde.org/Promo/Events/Parties/KDE_20_Anniversary)或[这里](http://s.wcd.im/v/158vrZ3a/)直接报名。

在此沙龙上，我们还会宣布成立 KDE 中国用户组 China KUG，您将成为 China KUG 的第一批成员。

*KDE 20 周年纪念沙龙*

来吧！和我们一起吃蛋糕、合影，分享 KDE 体会，还有机会获取 iSOFTLinux V4.0 U 盘、KDE 二十周年 T 恤等多种奖品。我们在这里真诚期待每一个 KDE 爱好者及开源软件爱好者的到来！

*KDE 20 周年纪念专属定制限量 T 恤*

#### 沙龙日程

|  |  |
| --- | --- |
| 13:30–14:00 | 签到 |
| 14:00-14:30 | KDE 20 年发展历史 （KDE 成员 Leslie Zhai 讲演） |
| 14:30-14:50 | 国际及国内 KDE KOL 对本次北京庆祝活动的祝贺视频 |
| 14:50–15:20 | KDE 组件打包 |
| 15:20-15:50 | KDE-China 翻译组介绍 |
| 15:50-16:10 | 20 周年庆典（蛋糕） |
| 16:20-16:40 | China KUG 成立仪式 |
| 16:40-17:00 | 游戏及合影 |

#### 沙龙地点

北京海淀区农大南路 1 号硅谷亮城 8 号楼 1 层布谷森林咖啡厅

请务必提前报名，好给您预留座位。


Fedora 24 的用户，千万不要在桌面里运行 `dnf update`

Summary: 如果你正在使用 Fedora 24 操作系统，当你在使用 KDE、GNOME、Xfce 等桌面环境时，千万不要执行 dnf update 命令。截止到现在，该 bug 仍无修复完成的通知。

tty 终端在 Fedora 从F2 开始

囧

尝试了下，没有崩溃阿。。。。

Gentoo用戶前來看戲...

ubuntu用户默默路过……

来自117.136.99.17的 Sina Weibo 6.8|Android 6.0 用户

原文有这么一句：“但是这与用户的硬件配置和安装的软件有关。”

Arch表示不慌

囧，刚输了

我就好奇，然后呢？

我也好奇，结果怎么样？

瞎说，我这几天每天都有更新，怎么没事？你是谁派来的？老实交待

是最近的包有问题吗？一直都是在图形化界面更新的，没有出现过问题哦。

是习惯了吗？哈哈！不过还是习惯用Arch

Thinkpad E450 赛扬芯，KDE版的试过了，确实有kernel updates not working的报错，但是不会崩。。。。已经手贱亲测。。。。。。

这玩意不光fedora吧！centos的gnome也挂过，后来就摸黑又更新到新的内核又好了。

为何我的 update 没事？

部分情况下有，我见到有人反馈遇到了。

摸黑更新内核——笑哭了。

连发三条——难道也是摸黑发的嘛。哈哈哈~

真是嘴贱啊我。。。

昨天再次 update 一下，今早 Gnome 就崩溃了。现在只有 Gnome on Wayland 可以用了，什么鬼。。。

哈哈哈哈，我能说我忍不住笑吗。

Linux Mint用户：看戏

# Fedora 24 的用户，千万不要在桌面里运行 `dnf update`

前几天，Fedora 项目组的 Adam Williamson [发布一则服务公告（PSA）](https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/7ULAG243UNGTOSL6URGNG23GC4B6X5GB/)，提醒 Fedora 社区在内部更新过程中出现了严重问题！

许多 Fedora 24 的用户报告称，从 4 日起，当他们在桌面中运行 `dnf update` 命令，通过官方软件仓库更新软件时会遇到 “duplicated packages” 和 “kernel updates not working” 等错误。

<img src="content_image/album_201610_06_205757x4a43qqavau64a4p.jpeg" alt="" title="">

经过一些调查，Williamson 得出结论在 dnf update 过程中存在一个 bug，会导致图形界面，比如 GNOME 或 KDE，甚至整个 X Window 系统（X11）崩溃，但是这与用户的硬件配置和安装的软件有关。

“当崩溃发生时，更新进程会被杀死以至于不能完整结束，这就是为什么会得到 ‘duplicated packages’ 或其它的奇怪错误的原因”，Adam Williamson 说，“我在努力和报告者配合调查原因，希望能够解决它，但是，此刻，**不要在桌面里面运行 `dnf update` !**”

所以，如果你正在使用 Fedora 24 操作系统，当你在使用 KDE、GNOME、Xfce 等桌面环境时，千万不要执行 `dnf update` 命令。你可以通过按下 Ctrl+Alt+F3 切换到文本模式来执行它，或者使用离线更新系统。

截止到现在，该 bug 仍无修复完成的通知。


一周开源新闻：Linux 4.8 内核发布

Summary:
更多：
- Ubuntu 16.10 可选 Unity 8
- dnf 2.0 里程碑版本发布
- KDE 5.8 发布

# 一周开源新闻：Linux 4.8 内核发布

### Linux 4.8 内核发布

*Linux 4.8 内核发布*

10 月 2 日，经过了两个月的开发，发布了 8 个 RC 版本后，Linus Torvalds 宣布 Linux 4.8 内核正式发布。Linux 4.8 内核会很快随着各个发行版的更新推送给 Linux 用户们，而即将发布的 Ubuntu 16.10 应该是第一个使用最新的 4.8 内核的主流发行版。

此外随着 4.8 的发布，同时也开启了 4.9 的合并窗口——宣告了 4.9 的开发开始。

不过，两天后，Linus 为新发布的 4.8 内核中包含的 bug 进行了[道歉](http://lkml.iu.edu/hypermail/linux/kernel/1610.0/00878.html)。该 bug 由内核开发者 Andrew Morton 引入到 4.8 发布前的最后一个 RC 版本当中，当编译这部分代码时会导致问题，从而编译出来的是一个死内核。显然， 为了应对这个问题，4.8.1 会很快发布。

### Ubuntu 16.10 终于支持可选 Unity 8 会话了

*可选的 Unity 8 会话*

即将在下周发布的 Ubuntu 16.10 发布了最后一个 Beta 版本，在这个版本中，Canonical 之前就说的要把 Unity 8 会话作为可选会话的承诺终于实现了。不过目前 Unity 8 仍然是一个试验性的会话，所以，仅供体验尝鲜。

此外，如上面说的，在这个最终 Beta 版本中，也使用了 Linux 4.8 内核。

### DNF 达到 2.0 里程碑

Fedora Linux 的 dnf 软件包管理器[发布](http://dnf.baseurl.org/2016/10/03/dnf-2-0-0-and-dnf-plugins-core-1-0-0-released/)了 2.0.0 版本，它出现在了 Fedora 的开发版本 Fedora Rawhide 之中，结束了 dnf 1.1 系列。

你可以看看 [dnf 2.0.0 的发布公告](http://dnf.readthedocs.org/en/latest/release_notes.html#release-notes)，但是由于和 1.x 的部分[不兼容性](http://dnf.readthedocs.io/en/latest/dnf-1_vs_dnf-2.html)，所以看起来不大可能会出现在 Fedora 25 之中。

此外，最近两天 Fedora 24 的 dnf 更新出现了严重错误，在该问题解决前，请勿在桌面环境中执行 `dnf update` 命令。详见：[Fedora 24 的用户，千万不要在桌面里运行 `dnf update`](/article-7836-1.html "Fedora 24 的用户，千万不要在桌面里运行 `dnf update`") 。

### KDE 5.8 LTS 发布

在 KDE 赢来 20 周年之际，KDE 开发团队宣布了 KDE 5.8 LTS 正式发布，这是官方认可的第一个长期支持版本，会一直支持到 2018 年。详见： [KDE 庆祝 20 岁生日，发布了首个 LTS 版本： KDE 5.8](/article-7835-1.html "KDE 庆祝 20 岁生日，发布了首个 LTS 版本： KDE 5.8") 。


揭秘 Twitter 背后的基础设施：效率与优化篇

# 揭秘 Twitter 背后的基础设施：效率与优化篇

过去我们曾经发布过一些关于 [Finagle](https://twitter.github.io/finagle/) 、[Manhattan](https://blog.twitter.com/2014/manhattan-our-real-time-multi-tenant-distributed-database-for-twitter-scale) 这些项目的文章，还写过一些针对大型事件活动的[架构优化](https://blog.twitter.com/2013/new-tweets-per-second-record-and-how)的文章，例如天空之城、超级碗、2014 世界杯、全球新年夜庆祝活动等。在这篇基础设施系列文章中，我主要聚焦于 Twitter 的一些关键设施和组件。我也会写一些我们在系统的扩展性、可靠性、效率方面的做过的改进，例如我们基础设施的历史，遇到过的挑战，学到的教训，做过的升级，以及我们现在前进的方向等等。

>
> 天空之城：2013 年 8 月 2 日，宫崎骏的《<ruby> 天空之城 <rp>  （ </rp> <rt>  Castle in the Sky </rt> <rp>  ） </rp></ruby>》在 NTV 迎来其第 14 次电视重播，剧情发展到高潮之时，Twitter 的 TPS（Tweets Per Second）也被推上了新的高度——143,199 TPS，是平均值的 25 倍，这个记录保持至今。-- LCTT 译注
>
>
>

### 数据中心的效率优化

#### 历史

当前 Twitter 硬件和数据中心的规模已经超过大多数公司。但达到这样的规模不是一蹴而就的，系统是随着软硬件的升级优化一步步成熟起来的，过程中我们也曾经犯过很多错误。

有个一时期我们的系统故障不断。软件问题、硬件问题，甚至底层设备问题不断爆发，常常导致系统运营中断。出现故障的地方存在于各个方面，必须综合考虑才能确定其风险和受到影响的服务。随着 Twitter 在客户、服务、媒体上的影响力不断扩大，构建一个高效、可靠的系统来提供服务成为我们的战略诉求。

>
> Twitter系统故障的界面被称为<ruby> 失败鲸 <rp>  （ </rp> <rt>  Fail Whale </rt> <rp>  ） </rp></ruby>，如下图 -- LCTT 译注
>
>
>
>
>
>

#### 挑战

一开始，我们的软件是直接安装在服务器，这意味着软件可靠性依赖硬件，电源、网络以及其他的环境因素都是威胁。这种情况下，如果要增加容错能力，就需要统筹考虑这些互不关联的物理设备因素及在上面运行的服务。

最早采购数据中心方案的时候，我们都还是菜鸟，对于站点选择、运营和设计都非常不专业。我们先直接托管主机，业务增长后我们改用租赁机房。早期遇到的问题主要是因为设备故障、数据中心设计问题、维护问题以及人为操作失误。我们也在持续迭代我们的硬件设计，从而增强硬件和数据中心的容错性。

服务中断的原因有很多，其中硬件故障常发生在服务器、机架交换机、核心交换机这地方。举一个我们曾经犯过的错误，硬件团队最初在设计服务器的时候，认为双路电源对减少供电问题的意义不大 -- 他们真的就移除了一块电源。然而数据中心一般给机架提供两路供电来提高冗余性，防止电网故障传导到服务器，而这需要两块电源。最终我们不得不在机架上增加了一个 ATS 单元（<ruby> 交流切换开关 <rp>  （ </rp> <rt>  AC transfer switch </rt> <rp>  ） </rp></ruby>）来接入第二路供电。

提高系统的可靠性靠的就是这样的改进，给网络、供电甚至机房增加冗余，从而将影响控制到最小范围。

#### 我们学到的教训以及技术的升级、迁移和选型

我们学到的第一个教训就是要先建模，将可能出故障的地方（例如建筑的供电和冷却系统、硬件、光纤网络等）和运行在上面的服务之间的依赖关系弄清楚，这样才能更好地分析，从而优化设计提升容错能力。

我们增加了更多的数据中心提升地理容灾能力，减少自然灾害的影响。而且这种站点隔离也降低了软件的风险，减少了例如软件部署升级和系统故障的风险。这种多活的数据中心架构提供了<ruby> 代码灰度发布 <rp>  （ </rp> <rt>  staged code deployment </rt> <rp>  ） </rp></ruby>的能力，减少代码首次上线时候的影响。

我们设计新硬件使之能够在更高温度下正常运行，数据中心的能源效率因此有所提升。

#### 下一步工作

随着公司的战略发展和运营增长，我们在不影响我们的最终用户的前提下，持续不断改进我们的数据中心。下一步工作主要是在当前能耗和硬件的基础上，通过维护和优化来提升效率。

### 硬件的效率优化

#### 历史和挑战

我们的硬件工程师团队刚成立的时候只能测试市面上现有硬件，而现在我们能自己定制硬件以节省成本并提升效率。

Twitter 是一个很大的公司，它对硬件的要求对任何团队来说都是一个不小的挑战。为了满足整个公司的需求，我们的首要工作是能检测并保证购买的硬件的品质。团队重点关注的是性能和可靠性这两部分。对于硬件我们会做系统性的测试来保证其性能可预测，保证尽量不引入新的问题。

随着我们一些关键组件的负荷越来越大（如 Mesos、Hadoop、Manhattan、MySQL 等），市面上的产品已经无法满足我们的需求。同时供应商提供的一些高级服务器功能，例如 Raid 管理或者电源热切换等，可靠性提升很小，反而会拖累系统性能而且价格高昂，例如一些 Raid 控制器价格高达系统总报价的三分之一，还拖累了 SSD 的性能。

那时，我们也是 MySQL 数据库的一个大型用户。SAS（<ruby> 串行连接 SCSI <rp>  （ </rp> <rt>  Serial Attached SCSI </rt> <rp>  ） </rp></ruby>）设备的供应和性能都有很大的问题。我们大量使用 1U 规格的服务器，它的磁盘和回写缓存一起也只能支撑每秒 2000 次的顺序 IO。为了获得更好的效果，我们只得不断增加 CPU 核心数并加强磁盘能力。我们那时候找不到更节省成本的方案。

后来随着我们对硬件需求越来越大，我们成立了一个硬件团队，从而自己来设计更便宜更高效的硬件。

#### 关键技术变更与选择

我们不断的优化硬件相关的技术，下面是我们采用的新技术和自研平台的时间轴。

* 2012 - 采用 SSD 作为我们 MySQL 和 Key-Value 数据库的主要存储。
* 2013 - 我们开发了第一个定制版 Hadoop 工作站，它现在是我们主要的大容量存储方案。
* 2013 - 我们定制的解决方案应用在 Mesos、TFE（ Twitter Front-End ）以及缓存设备上。
* 2014 - 我们定制的 SSD Key-Value 服务器完成开发。
* 2015 - 我们定制的数据库解决方案完成开发。
* 2016 - 我们开发了一个 GPU 系统来做模糊推理和训练机器学习。

#### 学到的教训

硬件团队的工作本质是通过做取舍来优化 TCO（总体拥有成本），最终达到达到降低 CAPEX（资本支出）和 OPEX（运营支出）的目的。概括来说，服务器降成本就是：

1. 删除无用的功能和组件
2. 提升利用率

Twitter 的设备总体来说有这四大类：存储设备、计算设备、数据库和 GPU 。 Twitter 对每一类都定义了详细的需求，让硬件工程师更针对性地设计产品，从而优化掉那些用不到或者极少用的冗余部分。例如，我们的存储设备就专门为 Hadoop 优化过，设备的购买和运营成本相比于 OEM 产品降低了 20% 。同时，这样做减法还提高了设备的性能和可靠性。同样的，对于计算设备，硬件工程师们也通过移除无用的特性获得了效率提升。

一个服务器可以移除的组件总是有限的，我们很快就把能移除的都扔掉了。于是我们想出了其他办法，例如在存储设备里，我们认为降低成本最好的办法是用一个节点替换多个节点，并通过 Aurora/Mesos 来管理任务负载。这就是我们现在正在做的东西。

对于这个我们自己新设计的服务器，首先要通过一系列的标准测试，然后会再做一系列负载测试，我们的目标是一台新设备至少能替换两台旧设备。最大的性能提升来自增加 CPU 的线程数，我们的测试结果表示新 CPU 的 单线程能力提高了 20~50% 。同时由于整个服务器的线程数增加，我们看到单线程能效提升了 25%。

这个新设备首次部署的时候，监控发现新设备只能替换 1.5 台旧设备，这比我们的目标低了很多。对性能数据检查后发现，我们之前对负载特性的一些假定是有问题的，而这正是我们在做性能测试需要发现的问题。

对此我们硬件团队开发了一个模型，用来预测在不同的硬件配置下当前 Aurora 任务的填充效率。这个模型正确的预测了新旧硬件的性能比例。模型还指出了我们一开始没有考虑到的存储需求，并因此建议我们增加 CPU 核心数。另外，它还预测，如果我们修改内存的配置，那系统的性能还会有较大提高。

硬件配置的改变都需要花时间去操作，所以我们的硬件工程师们就首先找出几个关键痛点。例如我们和 SRE（<ruby> 网站可靠性工程师 <rp>  （ </rp> <rt>  Site Reliability Engineer </rt> <rp>  ） </rp></ruby>）团队一起调整任务顺序来降低存储需求，这种修改很简单也很有效，新设备可以代替 1.85 个旧设备了。

为了更好的优化效率，我们对新硬件的配置做了修改，只是扩大了内存和磁盘容量就将 CPU 利用率提高了20% ，而这只增加了非常小的成本。同时我们的硬件工程师也和合作生产厂商一起为那些服务器的最初出货调整了物料清单。后续的观察发现我们的自己的新设备实际上可以代替 2.4 台旧设备，这个超出了预定的目标。

### 从裸设备迁移到 mesos 集群

直到 2012 年为止，软件团队在 Twitter 开通一个新服务还需要自己操心硬件：配置硬件的规格需求，研究机架尺寸，开发部署脚本以及处理硬件故障。同时，系统中没有所谓的“服务发现”机制，当一个服务需要调用一个另一个服务时候，需要读取一个 YAML 配置文件，这个配置文件中有目标服务对应的主机 IP 和端口信息（预留的端口信息是由一个公共 wiki 页面维护的）。随着硬件的替换和更新，YAML 配置文件里的内容也会不断的编辑更新。在缓存层做修改意味着我们可以按小时或按天做很多次部署，每次添加少量主机并按阶段部署。我们经常遇到在部署过程中 cache 不一致导致的问题，因为有的主机在使用旧的配置有的主机在用新的。有时候一台主机的异常（例如在部署过程中它临时宕机了）会导致整个站点都无法正常工作。

在 2012/2013 年的时候，Twitter 开始尝试两个新事物：服务发现（来自 ZooKeeper 集群和 [Finagle](https://twitter.github.io/finagle/) 核心模块中的一个库）和 [Mesos](http://mesos.apache.org/)（包括基于 Mesos 的一个自研的计划任务框架 Aurora ，它现在也是 Apache 基金会的一个项目）。

服务发现功能意味着不需要再维护一个静态 YAML 主机列表了。服务或者在启动后主动注册，或者自动被 mesos 接入到一个“服务集”（就是一个 ZooKeeper 中的 znode 列表，包含角色、环境和服务名信息）中。任何想要访问这个服务的组件都只需要监控这个路径就可以实时获取到一个正在工作的服务列表。

现在我们通过 Mesos/Aurora ，而不是使用脚本（我们曾经是 [Capistrano](https://github.com/capistrano/capistrano) 的重度用户）来获取一个主机列表、分发代码并规划重启任务。现在软件团队如果想部署一个新服务，只需要将软件包上传到一个叫 Packer 的工具上（它是一个基于 HDFS 的服务），再在 Aurora 配置上描述文件（需要多少 CPU ，多少内存，多少个实例，启动的命令行代码），然后 Aurora 就会自动完成整个部署过程。 Aurora 先找到可用的主机，从 Packer 下载代码，注册到“服务发现”，最后启动这个服务。如果整个过程中遇到失败（硬件故障、网络中断等等）， Mesos/Aurora 会自动重选一个新主机并将服务部署上去。

#### Twitter 的私有 PaaS 云平台

Mesos/Aurora 和服务发现这两个功能给我们带了革命性的变化。虽然在接下来几年里，我们碰到了无数 bug ，伤透了无数脑筋，学到了分布式系统里的无数教训，但是这套架还是非常赞的。以前大家一直忙于处理硬件搭配和管理，而现在，大家只需要考虑如何优化业务以及需要多少系统能力就可以了。同时，我们也从根本上解决了 Twitter 之前经历过的 CPU 利用率低的问题，以前服务直接安装在服务器上，这种方式无法充分利用服务器资源，任务协调能力也很差。现在 Mesos 允许我们把多个服务打包成一个服务包，增加一个新服务只需要修改配额，再改一行配置就可以了。

在两年时间里，多数“无状态”服务迁移到了 Mesos 平台。一些大型且重要的服务（包括我们的用户服务和广告服务系统）是最先迁移上去的。因为它们的体量巨大，所以它们从这些服务里获得的好处也最多，这也降低了它们的服务压力。

我们一直在不断追求效率提升和架构优化的最佳实践。我们会定期去测试公有云的产品，和我们自己产品的 TCO 以及性能做对比。我们也拥抱公有云的服务，事实上我们现在正在使用公有云产品。最后，这个系列的下一篇将会主要聚焦于我们基础设施的体量方面。

特别感谢 [Jennifer Fraser](https://twitter.com/jenniferfraser)、[David Barr](https://twitter.com/davebarr)、[Geoff Papilion](https://twitter.com/gpapilion)、 [Matt Singer](https://twitter.com/mattbytes)、[Lam Dong](https://twitter.com/lamdong) 对这篇文章的贡献。

---

via: <https://blog.twitter.com/2016/the-infrastructure-behind-twitter-efficiency-and-optimization>

作者：[mazdakh](https://twitter.com/intent/user?screen_name=mazdakh) 译者：[eriwoon](https://github.com/eriwoon) 校对：[wxy](https://github.com/wxy)

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


Ryver：你应该使用它替代 Slack

Summary: 貌似每个人都听说过 Slack，它是一款跨平台的，可以使你时刻保持与他人同步的团队沟通应用。它革新了用户讨论和规划项目的方式，显而易见，它升级了 email 的沟通功能。

# Ryver：你应该使用它替代 Slack

貌似每个人都听说过 [Slack](https://www.maketecheasier.com/new-slack-features/)，它是一款跨平台的，可以使你时刻保持与他人同步的团队沟通应用。它革新了用户讨论和规划项目的方式，显而易见，它升级了 email 的沟通功能。

我在一个非常小的写作团队工作，不管是通过手机还是电脑，我从未在使用 Slack 过程中遇到过沟通问题。若想与任何规模的团队保持同步，继续使用 Slack 仍然不失为不错的方式。

既然如此，为什么我们还要讨论今天的话题？Ryver 被人们认为是下一个热点，相比 Slack，Ryver 提供了升级版的服务。Ryver 完全免费，它的团队正在奋力争取更大的市场份额。

是否 Ryver 已经强大到可以扮演 Slack 杀手的角色？这两种旗鼓相当的消息应用究竟有何不同？

欲知详情，请阅读下文。

### 为什么用 Ryver ?

既然 Slack 能用为什么还要折腾呢？Ryver 的开发者对 Slack 的功能滚瓜烂熟，他们希望 Ryver 改进的服务足以让你移情别恋。他们承诺 Ryver 提供完全免费的团队沟通服务，并且不会在任何一个环节隐形收费。

谢天谢地，他们用高质量产品兑现了自己的承诺。

额外的内容是关键所在，他们承诺去掉一些你在 Slack 免费账号上面遇到的限制。无限的存储空间是一个加分点，除此之外，在许多其他方面 Ryver 也更加开放。如果存储空间限制对你来说是个痛点，不防试试 Ryver。

这是一个简单易用的系统，所有的功能都可以一键搞定。这种设计哲学使 Apple 大获成功。当你开始使用它之后，也不会遭遇成长的烦恼。

会话分为私聊和公示，这意味着团队平台和私人用途有明确的界限。它应该有助于避免将任何尴尬的广而告之给你的同事，这些问题我在使用 Slack 期间都遇到过。

Ryver 支持与大量现成的 App 的集成，并在大多数平台上有原生应用程序。

在需要时，你可以添加访客而无需增加费用，如果你经常和外部客户打交道，这将是一个非常有用的功能。访客可以增加更多的访客，这种流动性的元素是无法从其他更流行的消息应用中看到的。

考虑到 Ryver 是一个为迎合不同需求而产生的完全不同的服务。如果你需要一个账户来处理几个客户，Ryver 值得一试。

问题是它是如何做到免费的呢？ 简单的答案是高级用户将为你的使用付了费。 就像 Spotify 和其他应用一样，有一小部分人为我们其他人支付了费用。 这里有一个[直接链接](http://www.ryver.com/downloads/)到他们的下载页面的地址，如果有兴趣就去试一试吧。

### 你应该切换到 Ryver 吗？

像我一样在小团队使用 Slack 的体验还是非常棒，但是 Ryver 可以给予的更多。一个完全免费的团队沟通应用的想法不可谓不宏伟，更何况它工作的十分完美。

同时使用这两种消息应用也无可厚非，但是如果你不愿意为一个白金 Slack 账户付费，一定要尝试一下竞争对手的服务。你可能会发现，两者各擅胜场，这取决于你需要什么。

最重要的是，Ryver 是一个极棒的免费替代品，它不仅仅是一个 Slack 克隆。他们清楚地知道他们想要实现什么，他们有一个可以在拥挤不堪的市场提供不同的东西的不错的产品。

但是，如果将来持续缺乏资金，Ryver 有可能消失。 它可能会让你的团队和讨论陷入混乱。 目前一切还好，但是如果你计划把更大的业务委托给这个新贵还是需要三思而行。

如果你厌倦了 Slack 对免费帐户的限制，你会对 Ryver 印象深刻。 要了解更多，请访问其网站以获取有关服务的信息。

---

via: <https://www.maketecheasier.com/why-use-ryver-instead-of-slack/>

作者：[James Milin-Ashmore](https://www.maketecheasier.com/author/james-ashmore/) 译者：[firstadream](https://github.com/firstadream) 校对：[wxy](https://github.com/wxy)

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


12 个最佳的免费学习编程的游戏网站

貌似  很难打开

玩了第一个，E文不过关，不知道要做什么

codecombat  可以用 国人搭建的服务器 。http://www.icodegame.com/

这能提高编程水平吗

只有第二个打开了，前几关免费，后面付费

CodinGame怎么登陆呀

CodinGame怎么登陆呀

# 12 个最佳的免费学习编程的游戏网站

学习编程虽然对有些人来说是件乐事，但是对大多数人来说仍然是一件比较枯燥困难的事情。当然，面临这样困惑的人，并不是只有你一个，所以，这世界上就出现了许多寓教于乐的通过游戏的形式来教你编程的网站。

在这篇文章中，我们对 200 多个编程游戏网站的各个方面进行了评估，包括是否免费、是否自由开源、是面对菜鸟还是有经验的程序员、支持的编程语言等等，然后遴选出这 12 个上佳的免费的编程游戏网站，希望能让你或你的朋友（或者你的孩子）真正喜欢上编程。

### CodinGame

这是一个需要编程一个 AI 机器人，然后由它来解决各种面临的问题的有趣游戏。支持各种编程语言。

网站： <https://www.codingame.com/>

### Code Combat

CodeCombat 是一个在真正的游戏中教学生们学习计算机编程的平台。这是一个社区项目，有上百位玩家贡献者们创建了各种关卡。支持 Java、JavaScript、Python、Lua、CoffeeScript 等语言。

网站：<https://codecombat.com/play>

### Screeps

在游戏中学习 JavaScript 编程。该游戏号称它是世界上第一个面向程序员的大型多人在线（MMO）战略沙盒游戏。

网站： <https://screeps.com/>

### Check iO

Check iO 是一个基于浏览器的游戏，你需要使用 Python 或 JavaScript 来解决问题才能将游戏进行下去（需要登录）。

网站：<https://checkio.org/>

### Vim Adventures

通过游戏来学习 Vim。

网站： <http://vim-adventures.com/>

### Cyber DoJo

Cyber-dojo 是一个程序员实战编程的地方。支持 JavaScript、Java、Python、PHP、Ruby 等语言。

网站： <http://www.cyber-dojo.org/>

源代码：<https://github.com/JonJagger/cyber-dojo>

### Code Monkey

通过尝试捕获香蕉来学习编程。可以用来教孩子们学习编程（付费的）。

网站： <https://www.playcodemonkey.com/>

### Elevator Saga

电梯编程游戏，需要一步步解决问题过关。比如说第一关是在 60 秒内运输 15 个人。

网站： <http://play.elevatorsaga.com/>

源代码：<https://github.com/magwo/elevatorsaga>

### Codewars

通过挑战来精湛编程技艺。在真实的编程挑战中提升技能。支持 JavaScript、Python、C#、Java、Python 等语言。

网站： <http://www.codewars.com/>

### Ruby Quiz

Ruby Quiz 是一个面向 Ruby 程序员的每周编程挑战，当前已经有 156 个竞赛挑战。

网站： <http://rubyquiz.com/>

### Git Games

git-game 是一个基于终端的游戏，它用来教授 git 中的那些非常酷的功能。游戏的主题内容是搜寻，通过 git 命令来找到线索，解开谜题。

网站： <http://www.git-game.com/>

### Hacker Org

hacker.org 挑战赛是一系列的拼图、敲砖块、测试和智力题，用来测试你的黑客能力。要通过这一系列的考验，你需要破解加密、写出精巧的代码，揭开重重迷雾。

网站： <http://www.hacker.org/>

好了，以上就是我们挑选出来的 12 个编程游戏，何不试试？


搭个 Web 服务器（三）

感谢分享！

楼主，想问一下mac是不是无法查看僵尸进程？我在mac下运行下面这段代码：

#include <unistd.h>

int main(void)

{

        int i;

        for (i = 0; i < 10; ++i) {

                if (!fork()) {

                        return 0;

                }

        }

        sleep(100);

        return 0;

}

编译成main,然后ps -ef | grep 'main'之后根本无法查看到<defunct>的进程啊。

看 STATE 字段里面 Z 的进程，这是僵尸、

# 搭个 Web 服务器（三）

>
> “只有在创造中才能够学到更多。” ——皮亚杰
>
>
>

在本系列的[第二部分](/article-7685-1.html)中，你创造了一个可以处理基本 HTTP GET 请求的、朴素的 WSGI 服务器。当时我问了一个问题：“你该如何让你的服务器在同一时间处理多个请求呢？”在这篇文章中，你会找到答案。系好安全带，我们要认真起来，全速前进了！你将会体验到一段非常快速的旅程。准备好你的 Linux、Mac OS X（或者其他 \*nix 系统），还有你的 Python。本文中所有源代码均可在 [GitHub](https://github.com/rspivak/lsbaws/blob/master/part3/) 上找到。

### 服务器的基本结构及如何处理请求

首先，我们来回顾一下 Web 服务器的基本结构，以及服务器处理来自客户端的请求时，所需的必要步骤。你在[第一部分](/article-7662-1.html)及[第二部分](/article-7685-1.html)中创建的轮询服务器只能够一次处理一个请求。在处理完当前请求之前，它不能够接受新的客户端连接。所有请求为了等待服务都需要排队，在服务繁忙时，这个队伍可能会排的很长，一些客户端可能会感到不开心。

这是轮询服务器 [webserver3a.py](https://github.com/rspivak/lsbaws/blob/master/part3/webserver3a.py) 的代码：

```
#####################################################################
# 轮询服务器 - webserver3a.py                                       #
#                                                                   #
# 使用 Python 2.7.9 或 3.4                                          #
# 在 Ubuntu 14.04 及 Mac OS X 环境下测试通过                        #
#####################################################################
import socket

SERVER_ADDRESS = (HOST, PORT) = '', 8888
REQUEST_QUEUE_SIZE = 5

def handle_request(client_connection):
    request = client_connection.recv(1024)
    print(request.decode())
    http_response = b"""\
HTTP/1.1 200 OK

Hello, World!
"""
    client_connection.sendall(http_response)

def serve_forever():
    listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    listen_socket.bind(SERVER_ADDRESS)
    listen_socket.listen(REQUEST_QUEUE_SIZE)
    print('Serving HTTP on port {port} ...'.format(port=PORT))

    while True:
        client_connection, client_address = listen_socket.accept()
        handle_request(client_connection)
        client_connection.close()

if __name__ == '__main__':
    serve_forever()

```

为了观察到你的服务器在同一时间只能处理一个请求的行为，我们对服务器的代码做一点点修改：在将响应发送至客户端之后，将程序阻塞 60 秒。这个修改只需要一行代码，来告诉服务器进程暂停 60 秒钟。

这是我们更改后的代码，包含暂停语句的服务器 [webserver3b.py](https://github.com/rspivak/lsbaws/blob/master/part3/webserver3b.py)：

```
######################################################################
# 轮询服务器 - webserver3b.py                                         #
#                                                                    #
# 使用 Python 2.7.9 或 3.4                                            #
# 在 Ubuntu 14.04 及 Mac OS X 环境下测试通过                           #
#                                                                    #
# - 服务器向客户端发送响应之后，会阻塞 60 秒                             #
######################################################################
import socket
import time

SERVER_ADDRESS = (HOST, PORT) = '', 8888
REQUEST_QUEUE_SIZE = 5

def handle_request(client_connection):
    request = client_connection.recv(1024)
    print(request.decode())
    http_response = b"""\
HTTP/1.1 200 OK

Hello, World!
"""
    client_connection.sendall(http_response)
    time.sleep(60)  ### 睡眠语句，阻塞该进程 60 秒

def serve_forever():
    listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    listen_socket.bind(SERVER_ADDRESS)
    listen_socket.listen(REQUEST_QUEUE_SIZE)
    print('Serving HTTP on port {port} ...'.format(port=PORT))

    while True:
        client_connection, client_address = listen_socket.accept()
        handle_request(client_connection)
        client_connection.close()

if __name__ == '__main__':
    serve_forever()

```

用以下命令启动服务器：

```
$ python webserver3b.py

```

现在，打开一个新的命令行窗口，然后运行 `curl` 语句。你应该可以立刻看到屏幕上显示的字符串“Hello, World!”：

```
$ curl http://localhost:8888/hello
Hello, World!

```

然后，立刻打开第二个命令行窗口，运行相同的 `curl` 命令：

```
$ curl http://localhost:8888/hello

```

如果你在 60 秒之内完成了以上步骤，你会看到第二条 `curl` 指令不会立刻产生任何输出，而只是挂在了哪里。同样，服务器也不会在标准输出流中输出新的请求内容。这是这个过程在我的 Mac 电脑上的运行结果（在右下角用黄色框标注出来的窗口中，我们能看到第二个 `curl` 指令被挂起，正在等待连接被服务器接受）：

当你等待足够长的时间（60 秒以上）后，你会看到第一个 `curl` 程序完成，而第二个 `curl` 在屏幕上输出了“Hello, World!”，然后休眠 60 秒，进而终止。

这样运行的原因是因为在服务器在处理完第一个来自 `curl` 的请求之后，只有等待 60 秒才能开始处理第二个请求。这个处理请求的过程按顺序进行（也可以说，迭代进行），一步一步进行，在我们刚刚给出的例子中，在同一时间内只能处理一个请求。

现在，我们来简单讨论一下客户端与服务器的交流过程。为了让两个程序在网络中互相交流，它们必须使用套接字。你应当在本系列的前两部分中见过它几次了。但是，套接字是什么？

<ruby> 套接字 <rp>  （ </rp> <rt>  socket </rt> <rp>  ） </rp></ruby>是一个通讯通道<ruby> 端点 <rp>  （ </rp> <rt>  endpoint </rt> <rp>  ） </rp></ruby>的抽象描述，它可以让你的程序通过文件描述符来与其它程序进行交流。在这篇文章中，我只会单独讨论 Linux 或 Mac OS X 中的 TCP/IP 套接字。这里有一个重点概念需要你去理解：TCP <ruby> 套接字对 <rp>  （ </rp> <rt>  socket pair </rt> <rp>  ） </rp></ruby>。

>
> TCP 连接使用的套接字对是一个由 4 个元素组成的元组，它确定了 TCP 连接的两端：本地 IP 地址、本地端口、远端 IP 地址及远端端口。一个套接字对唯一地确定了网络中的每一个 TCP 连接。在连接一端的两个值：一个 IP 地址和一个端口，通常被称作一个套接字。（引自[《UNIX 网络编程 卷1：套接字联网 API （第3版）》](http://www.epubit.com.cn/book/details/1692)）
>
>
>

所以，元组 `{10.10.10.2:49152, 12.12.12.3:8888}` 就是一个能够在客户端确定 TCP 连接两端的套接字对，而元组 `{12.12.12.3:8888, 10.10.10.2:49152}` 则是在服务端确定 TCP 连接两端的套接字对。在这个例子中，确定 TCP 服务端的两个值（IP 地址 `12.12.12.3` 及端口 `8888`），代表一个套接字；另外两个值则代表客户端的套接字。

一个服务器创建一个套接字并开始建立连接的基本工作流程如下：

1. 服务器创建一个 TCP/IP 套接字。我们可以用这条 Python 语句来创建：

```
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

```
2. 服务器可能会设定一些套接字选项（这个步骤是可选的，但是你可以看到上面的服务器代码做了设定，这样才能够在重启服务器时多次复用同一地址）：

```
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

```
3. 然后，服务器绑定一个地址。绑定函数 `bind` 可以将一个本地协议地址赋给套接字。若使用 TCP 协议，调用绑定函数 `bind` 时，需要指定一个端口号，一个 IP 地址，或两者兼有，或两者全无。（引自[《UNIX网络编程 卷1：套接字联网 API （第3版）》](http://www.epubit.com.cn/book/details/1692)）

```
listen_socket.bind(SERVER_ADDRESS)

```
4. 然后，服务器开启套接字的监听模式。

```
listen_socket.listen(REQUEST_QUEUE_SIZE)

```

监听函数 `listen` 只应在服务端调用。它会通知操作系统内核，表明它会接受所有向该套接字发送的入站连接请求。

以上四步完成后，服务器将循环接收来自客户端的连接，一次循环处理一条。当有连接可用时，接受请求函数 `accept` 将会返回一个已连接的客户端套接字。然后，服务器从这个已连接的客户端套接字中读取请求数据，将数据在其标准输出流中输出出来，并向客户端回送一条消息。然后，服务器会关闭这个客户端连接，并准备接收一个新的客户端连接。

这是客户端使用 TCP/IP 协议与服务器通信的必要步骤：

下面是一段示例代码，使用这段代码，客户端可以连接你的服务器，发送一个请求，并输出响应内容：

```
import socket

### 创建一个套接字，并连接值服务器
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 8888))

### 发送一段数据，并接收响应数据
sock.sendall(b'test')
data = sock.recv(1024)
print(data.decode())

```

在创建套接字后，客户端需要连接至服务器。我们可以调用连接函数 `connect` 来完成这个操作：

```
sock.connect(('localhost', 8888))

```

客户端只需提供待连接的远程服务器的 IP 地址（或主机名），及端口号，即可连接至远端服务器。

你可能已经注意到了，客户端不需要调用 `bind` 及 `accept` 函数，就可以与服务器建立连接。客户端不需要调用 `bind` 函数是因为客户端不需要关注本地 IP 地址及端口号。操作系统内核中的 TCP/IP 协议栈会在客户端调用 `connect` 函数时，自动为套接字分配本地 IP 地址及本地端口号。这个本地端口被称为<ruby> 临时端口 <rp>  （ </rp> <rt>  ephemeral port </rt> <rp>  ） </rp></ruby>，即一个短暂开放的端口。

服务器中有一些端口被用于承载一些众所周知的服务，它们被称作<ruby> 通用 <rp>  （ </rp> <rt>  well-known </rt> <rp>  ） </rp></ruby>端口：如 80 端口用于 HTTP 服务，22 端口用于 SSH 服务。打开你的 Python shell，与你在本地运行的服务器建立一个连接，来看看内核给你的客户端套接字分配了哪个临时端口（在尝试这个例子之前，你需要运行服务器程序 `webserver3a.py` 或 `webserver3b.py`）：

```
>>> import socket
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sock.connect(('localhost', 8888))
>>> host, port = sock.getsockname()[:2]
>>> host, port
('127.0.0.1', 60589)

```

在上面的例子中，内核将临时端口 60589 分配给了你的套接字。

在我开始回答我在第二部分中提出的问题之前，我还需要快速讲解一些概念。你很快就会明白这些概念为什么非常重要。这两个概念，一个是进程，另外一个是文件描述符。

什么是进程？进程就是一个程序执行的实体。举个例子：当你的服务器代码被执行时，它会被载入内存，而内存中表现此次程序运行的实体就叫做进程。内核记录了进程的一系列有关信息——比如进程 ID——来追踪它的运行情况。当你在执行轮询服务器 `webserver3a.py` 或 `webserver3b.py` 时，你其实只是启动了一个进程。

我们在终端窗口中运行 `webserver3b.py`：

```
$ python webserver3b.py

```

在另一个终端窗口中，我们可以使用 `ps` 命令获取该进程的相关信息：

```
$ ps | grep webserver3b | grep -v grep
7182 ttys003    0:00.04 python webserver3b.py

```

`ps` 命令显示，我们刚刚只运行了一个 Python 进程 `webserver3b.py`。当一个进程被创建时，内核会为其分配一个进程 ID，也就是 PID。在 UNIX 中，所有用户进程都有一个父进程；当然，这个父进程也有进程 ID，叫做父进程 ID，缩写为 PPID。假设你默认使用 BASH shell，那当你启动服务器时，就会启动一个新的进程，同时被赋予一个 PID，而它的父进程 PID 会被设为 BASH shell 的 PID。

自己尝试一下，看看这一切都是如何工作的。重新开启你的 Python shell，它会创建一个新进程，然后在其中使用系统调用 `os.getpid()` 及 `os.getppid()` 来获取 Python shell 进程的 PID 及其父进程 PID（也就是你的 BASH shell 的 PID）。然后，在另一个终端窗口中运行 `ps` 命令，然后用 `grep` 来查找 PPID（父进程 ID，在我的例子中是 3148）。在下面的屏幕截图中，你可以看到一个我的 Mac OS X 系统中关于进程父子关系的例子，在这个例子中，子进程是我的 Python shell 进程，而父进程是 BASH shell 进程：

另外一个需要了解的概念，就是文件描述符。什么是文件描述符？文件描述符是一个非负整数，当进程打开一个现有文件、创建新文件或创建一个新的套接字时，内核会将这个数返回给进程。你以前可能听说过，在 UNIX 中，一切皆是文件。内核会按文件描述符来找到一个进程所打开的文件。当你需要读取文件或向文件写入时，我们同样通过文件描述符来定位这个文件。Python 提供了高层次的操作文件（或套接字）的对象，所以你不需要直接通过文件描述符来定位文件。但是，在高层对象之下，我们就是用它来在 UNIX 中定位文件及套接字，通过这个整数的文件描述符。

一般情况下，UNIX shell 会将一个进程的标准输入流（STDIN）的文件描述符设为 0，标准输出流（STDOUT）设为 1，而标准错误打印（STDERR）的文件描述符会被设为 2。

我之前提到过，即使 Python 提供了高层次的文件对象或类文件对象来供你操作，你仍然可以在对象上使用 `fileno()` 方法，来获取与该文件相关联的文件描述符。回到 Python shell 中，我们来看看你该怎么做到这一点：

```
>>> import sys
>>> sys.stdin
<open file '<stdin>', mode 'r' at 0x102beb0c0>
>>> sys.stdin.fileno()
0
>>> sys.stdout.fileno()
1
>>> sys.stderr.fileno()
2

```

当你在 Python 中操作文件及套接字时，你可能会使用高层次的文件/套接字对象，但是你仍然有可能会直接使用文件描述符。下面有一个例子，来演示如何用文件描述符做参数来进行一次写入的系统调用：

```
>>> import sys
>>> import os
>>> res = os.write(sys.stdout.fileno(), 'hello\n')
hello

```

下面是比较有趣的部分——不过你可能不会为此感到惊讶，因为你已经知道在 Unix 中，一切皆为文件——你的套接字对象同样有一个相关联的文件描述符。和刚才操纵文件时一样，当你在 Python 中创建一个套接字时，你会得到一个对象而不是一个非负整数，但你永远可以用我之前提到过的 `fileno()` 方法获取套接字对象的文件描述符，并可以通过这个文件描述符来直接操纵套接字。

```
>>> import socket
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> sock.fileno()
3

```

我还想再提一件事：不知道你有没有注意到，在我们的第二个轮询服务器 `webserver3b.py` 中，当你的服务器休眠 60 秒的过程中，你仍然可以通过第二个 `curl` 命令连接至服务器。当然 `curl` 命令并没有立刻输出任何内容而是挂在哪里，但是既然服务器没有接受连接，那它为什么不立即拒绝掉连接，而让它还能够继续与服务器建立连接呢？这个问题的答案是：当我在调用套接字对象的 `listen` 方法时，我为该方法提供了一个 `BACKLOG` 参数，在代码中用 `REQUEST_QUEUE_SIZE` 常量来表示。`BACKLOG` 参数决定了在内核中为存放即将到来的连接请求所创建的队列的大小。当服务器 `webserver3b.py` 在睡眠的时候，你运行的第二个 `curl` 命令依然能够连接至服务器，因为内核中用来存放即将接收的连接请求的队列依然拥有足够大的可用空间。

尽管增大 `BACKLOG` 参数并不能神奇地使你的服务器同时处理多个请求，但当你的服务器很繁忙时，将它设置为一个较大的值还是相当重要的。这样，在你的服务器调用 `accept` 方法时，不需要再等待一个新的连接建立，而可以立刻直接抓取队列中的第一个客户端连接，并不加停顿地立刻处理它。

欧耶！现在你已经了解了一大块内容。我们来快速回顾一下我们刚刚讲解的知识（当然，如果这些对你来说都是基础知识的话，那我们就当复习好啦）。

* 轮询服务器
* 服务端套接字创建流程（创建套接字，绑定，监听及接受）
* 客户端连接创建流程（创建套接字，连接）
* 套接字对
* 套接字
* 临时端口及通用端口
* 进程
* 进程 ID（PID），父进程 ID（PPID），以及进程父子关系
* 文件描述符
* 套接字的 `listen` 方法中，`BACKLOG` 参数的含义

### 如何并发处理多个请求

现在，我可以开始回答第二部分中的那个问题了：“你该如何让你的服务器在同一时间处理多个请求呢？”或者换一种说法：“如何编写一个并发服务器？”

在 UNIX 系统中编写一个并发服务器最简单的方法，就是使用系统调用 `fork()`。

下面是全新出炉的并发服务器 [webserver3c.py](https://github.com/rspivak/lsbaws/blob/master/part3/webserver3c.py) 的代码，它可以同时处理多个请求（和我们之前的例子 [webserver3b.py](https://github.com/rspivak/lsbaws/blob/master/part3/webserver3b.py) 一样，每个子进程都会休眠 60 秒）：

```
#######################################################
# 并发服务器 - webserver3c.py                          #
#                                                     #
# 使用 Python 2.7.9 或 3.4                             #
# 在 Ubuntu 14.04 及 Mac OS X 环境下测试通过            #
#                                                     #
# - 完成客户端请求处理之后，子进程会休眠 60 秒             #
# - 父子进程会关闭重复的描述符                           #
#                                                     #
#######################################################
import os
import socket
import time

SERVER_ADDRESS = (HOST, PORT) = '', 8888
REQUEST_QUEUE_SIZE = 5

def handle_request(client_connection):
    request = client_connection.recv(1024)
    print(
        'Child PID: {pid}. Parent PID {ppid}'.format(
            pid=os.getpid(),
            ppid=os.getppid(),
        )
    )
    print(request.decode())
    http_response = b"""\
HTTP/1.1 200 OK

Hello, World!
"""
    client_connection.sendall(http_response)
    time.sleep(60)

def serve_forever():
    listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    listen_socket.bind(SERVER_ADDRESS)
    listen_socket.listen(REQUEST_QUEUE_SIZE)
    print('Serving HTTP on port {port} ...'.format(port=PORT))
    print('Parent PID (PPID): {pid}\n'.format(pid=os.getpid()))

    while True:
        client_connection, client_address = listen_socket.accept()
        pid = os.fork()
        if pid == 0:  ### 子进程
            listen_socket.close()  ### 关闭子进程中复制的套接字对象
            handle_request(client_connection)
            client_connection.close()
            os._exit(0)  ### 子进程在这里退出
        else:  ### 父进程
            client_connection.close()  ### 关闭父进程中的客户端连接对象，并循环执行

if __name__ == '__main__':
    serve_forever()

```

在深入研究代码、讨论 `fork` 如何工作之前，先尝试运行它，自己看一看这个服务器是否真的可以同时处理多个客户端请求，而不是像轮询服务器 `webserver3a.py` 和 `webserver3b.py` 一样。在命令行中使用如下命令启动服务器：

```
$ python webserver3c.py

```

然后，像我们之前测试轮询服务器那样，运行两个 `curl` 命令，来看看这次的效果。现在你可以看到，即使子进程在处理客户端请求后会休眠 60 秒，但它并不会影响其它客户端连接，因为他们都是由完全独立的进程来处理的。你应该看到你的 `curl` 命令立即输出了“Hello, World!”然后挂起 60 秒。你可以按照你的想法运行尽可能多的 `curl` 命令（好吧，并不能运行特别特别多 `^_^`），所有的命令都会立刻输出来自服务器的响应 “Hello, World!”，并不会出现任何可被察觉到的延迟行为。试试看吧。

如果你要理解 `fork()`，那最重要的一点是：**你调用了它一次，但是它会返回两次** —— 一次在父进程中，另一次是在子进程中。当你创建了一个新进程，那么 `fork()` 在子进程中的返回值是 0。如果是在父进程中，那 `fork()` 函数会返回子进程的 PID。

我依然记得在第一次看到它并尝试使用 `fork()` 的时候，我是多么的入迷。它在我眼里就像是魔法一样。这就好像我在读一段顺序执行的代码，然后“砰！”地一声，代码变成了两份，然后出现了两个实体，同时并行地运行相同的代码。讲真，那个时候我觉得它真的跟魔法一样神奇。

当父进程创建出一个新的子进程时，子进程会复制从父进程中复制一份文件描述符：

你可能注意到，在上面的代码中，父进程关闭了客户端连接：

```
else:  ### 父进程
    client_connection.close()  # 关闭父进程的副本并循环

```

不过，既然父进程关闭了这个套接字，那为什么子进程仍然能够从来自客户端的套接字中读取数据呢？答案就在上面的图片中。内核会使用描述符引用计数器来决定是否要关闭一个套接字。当你的服务器创建一个子进程时，子进程会复制父进程的所有文件描述符，内核中该描述符的引用计数也会增加。如果只有一个父进程及一个子进程，那客户端套接字的文件描述符引用数应为 2；当父进程关闭客户端连接的套接字时，内核只会减少它的引用计数，将其变为 1，但这仍然不会使内核关闭该套接字。子进程也关闭了父进程中 `listen_socket` 的复制实体，因为子进程不需要关注新的客户端连接，而只需要处理已建立的客户端连接中的请求。

```
listen_socket.close()  ### 关闭子进程中的复制实体

```

我们将会在后文中讨论，如果你不关闭那些重复的描述符，会发生什么。

你可以从你的并发服务器源码中看到，父进程的主要职责为：接受一个新的客户端连接，复制出一个子进程来处理这个连接，然后继续循环来接受另外的客户端连接，仅此而已。服务器父进程并不会处理客户端连接——子进程才会做这件事。

打个岔：当我们说两个事件并发执行时，我们所要表达的意思是什么？

当我们说“两个事件并发执行”时，它通常意味着这两个事件同时发生。简单来讲，这个定义没问题，但你应该记住它的严格定义：

>
> 如果你不能在代码中判断两个事件的发生顺序，那这两个事件就是并发执行的。（引自[《信号系统简明手册 (第二版): 并发控制深入浅出及常见错误》](http://www.amazon.com/gp/product/1441418687/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1441418687&linkCode=as2&tag=russblo0b-20&linkId=QFOAWARN62OWTWUG)）
>
>
>

好的，现在你又该回顾一下你刚刚学过的知识点了。

* 在 Unix 中，编写一个并发服务器的最简单的方式——使用 `fork()` 系统调用；
* 当一个进程分叉（`fork`）出另一个进程时，它会变成刚刚分叉出的进程的父进程；
* 在进行 `fork` 调用后，父进程和子进程共享相同的文件描述符；
* 系统内核通过描述符的引用计数来决定是否要关闭该描述符对应的文件或套接字；
* 服务器父进程的主要职责：现在它做的只是从客户端接受一个新的连接，分叉出子进程来处理这个客户端连接，然后开始下一轮循环，去接收新的客户端连接。

### 进程分叉后不关闭重复的套接字会发生什么？

我们来看看，如果我们不在父进程与子进程中关闭重复的套接字描述符会发生什么。下面是刚才的并发服务器代码的修改版本，这段代码（[webserver3d.py](https://github.com/rspivak/lsbaws/blob/master/part3/webserver3d.py) 中，服务器不会关闭重复的描述符）：

```
#######################################################
# 并发服务器 - webserver3d.py                          #
#                                                     #
# 使用 Python 2.7.9 或 3.4                             #
# 在 Ubuntu 14.04 及 Mac OS X 环境下测试通过            #
#######################################################
import os
import socket

SERVER_ADDRESS = (HOST, PORT) = '', 8888
REQUEST_QUEUE_SIZE = 5

def handle_request(client_connection):
    request = client_connection.recv(1024)
    http_response = b"""\
HTTP/1.1 200 OK

Hello, World!
"""
    client_connection.sendall(http_response)

def serve_forever():
    listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    listen_socket.bind(SERVER_ADDRESS)
    listen_socket.listen(REQUEST_QUEUE_SIZE)
    print('Serving HTTP on port {port} ...'.format(port=PORT))

    clients = []
    while True:
        client_connection, client_address = listen_socket.accept()
        ### 将引用存储起来，否则在下一轮循环时，他们会被垃圾回收机制销毁
        clients.append(client_connection)
        pid = os.fork()
        if pid == 0:  ### 子进程
            listen_socket.close()  ### 关闭子进程中多余的套接字
            handle_request(client_connection)
            client_connection.close()
            os._exit(0)  ### 子进程在这里结束
        else:  ### 父进程
            # client_connection.close()
            print(len(clients))

if __name__ == '__main__':
    serve_forever()

```

用以下命令来启动服务器：

```
$ python webserver3d.py

```

用 `curl` 命令连接服务器：

```
$ curl http://localhost:8888/hello
Hello, World!

```

好，`curl` 命令输出了来自并发服务器的响应内容，但程序并没有退出，而是仍然挂起。到底发生了什么？这个服务器并不会挂起 60 秒：子进程只处理客户端连接，关闭连接然后退出，但客户端的 `curl` 命令并没有终止。

所以，为什么 `curl` 不终止呢？原因就在于文件描述符的副本。当子进程关闭客户端连接时，系统内核会减少客户端套接字的引用计数，将其变为 1。服务器子进程退出了，但客户端套接字并没有被内核关闭，因为该套接字的描述符引用计数并没有变为 0，所以，这就导致了连接终止包（在 TCP/IP 协议中称作 `FIN`）不会被发送到客户端，所以客户端会一直保持连接。这里也会出现另一个问题：如果你的服务器长时间运行，并且不关闭文件描述符的副本，那么可用的文件描述符会被消耗殆尽：

使用 `Control-C` 关闭服务器 `webserver3d.py`，然后在 shell 中使用内置命令 `ulimit` 来查看系统默认为你的服务器进程分配的可用资源数：

```
$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 3842
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 3842
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

```

你可以从上面的结果看到，在我的 Ubuntu 机器中，系统为我的服务器进程分配的最大可用文件描述符（文件打开）数为 1024。

现在我们来看一看，如果你的服务器不关闭重复的描述符，它会如何消耗可用的文件描述符。在一个已有的或新建的终端窗口中，将你的服务器进程的最大可用文件描述符设为 256：

```
$ ulimit -n 256

```

在你刚刚运行 `ulimit -n 256` 的终端窗口中运行服务器 [webserver3d.py](https://github.com/rspivak/lsbaws/blob/master/part3/webserver3d.py)：

```
$ python webserver3d.py

```

然后使用下面的客户端 [client3.py](https://github.com/rspivak/lsbaws/blob/master/part3/client3.py) 来测试你的服务器。

```
#######################################################
# 测试客户端 - client3.py                              #
#                                                     #
# 使用 Python 2.7.9 或 3.4                             #
# 在 Ubuntu 14.04 及 Mac OS X 环境下测试通过            #
#######################################################
import argparse
import errno
import os
import socket

SERVER_ADDRESS = 'localhost', 8888
REQUEST = b"""\
GET /hello HTTP/1.1
Host: localhost:8888

"""

def main(max_clients, max_conns):
    socks = []
    for client_num in range(max_clients):
        pid = os.fork()
        if pid == 0:
            for connection_num in range(max_conns):
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.connect(SERVER_ADDRESS)
                sock.sendall(REQUEST)
                socks.append(sock)
                print(connection_num)
                os._exit(0)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Test client for LSBAWS.',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    parser.add_argument(
        '--max-conns',
        type=int,
        default=1024,
        help='Maximum number of connections per client.'
    )
    parser.add_argument(
        '--max-clients',
        type=int,
        default=1,
        help='Maximum number of clients.'
    )
    args = parser.parse_args()
    main(args.max_clients, args.max_conns)

```

在一个新建的终端窗口中，运行 `client3.py` 然后让它与服务器同步创建 300 个连接：

```
$ python client3.py --max-clients=300

```

过一会，你的服务器进程就该爆了。这是我的环境中出现的异常截图：

这个例子很明显——你的服务器应该关闭描述符副本。

#### 僵尸进程

但是，即使你关闭了描述符副本，你依然没有摆脱险境，因为你的服务器还有一个问题，这个问题在于“<ruby> 僵尸 <rp>  （ </rp> <rt>  zombies </rt> <rp>  ） </rp></ruby>”！

没错，这个服务器代码确实在制造僵尸进程。我们来看看怎么回事。重新运行你的服务器：

```
$ python webserver3d.py

```

在另一个终端窗口中运行以下 `curl` 命令：

```
$ curl http://localhost:8888/hello

```

现在，运行 `ps` 环境，来查看正在运行的 Python 进程。下面是我的环境中 `ps` 的运行结果：

```
$ ps auxw | grep -i python | grep -v grep
vagrant   9099  0.0  1.2  31804  6256 pts/0    S+   16:33   0:00 python webserver3d.py
vagrant   9102  0.0  0.0      0     0 pts/0    Z+   16:33   0:00 [python] <defunct>

```

你看到第二行中，pid 为 9102，状态为 `Z+`，名字里面有个 `<defunct>` 的进程了吗？那就是我们的僵尸进程。这个僵尸进程的问题在于：你无法将它杀掉！

就算你尝试使用 `kill -9` 来杀死僵尸进程，它们仍旧会存活。自己试试看，看看结果。

这个僵尸到底是什么，为什么我们的服务器会造出它们呢？一个<ruby> 僵尸进程 <rp>  （ </rp> <rt>  zombie </rt> <rp>  ） </rp></ruby>是一个已经结束的进程，但它的父进程并没有等待（`waited`）它结束，并且也没有收到它的终结状态。如果一个进程在父进程退出之前退出，系统内核会把它变为一个僵尸进程，存储它的部分信息，以便父进程读取。内核保存的进程信息通常包括进程 ID、进程终止状态，以及进程的资源占用情况。OK，所以僵尸进程确实有存在的意义，但如果服务器不管这些僵尸进程，你的系统将会被壅塞。我们来看看这个会如何发生。首先，关闭你运行的服务器；然后，在一个新的终端窗口中，使用 `ulimit` 命令将最大用户进程数设为 400（同时，要确保你的最大可用描述符数大于这个数字，我们在这里设为 500）：

```
$ ulimit -u 400
$ ulimit -n 500

```

在你刚刚运行 `ulimit -u 400` 命令的终端中，运行服务器 `webserver3d.py`：

```
$ python webserver3d.py

```

在一个新的终端窗口中，运行 `client3.py`，并且让它与服务器同时创建 500 个连接：

```
$ python client3.py --max-clients=500

```

然后，过一会，你的服务器进程应该会再次爆了，它会在创建新进程时抛出一个 `OSError: 资源暂时不可用` 的异常。但它并没有达到系统允许的最大进程数。这是我的环境中输出的异常信息截图：

你可以看到，如果服务器不管僵尸进程，它们会引发问题。接下来我会简单探讨一下僵尸进程问题的解决方案。

我们来回顾一下你刚刚掌握的知识点：

* 如果你不关闭文件描述符副本，客户端就不会在请求处理完成后终止，因为客户端连接没有被关闭；
* 如果你不关闭文件描述符副本，长久运行的服务器最终会把可用的文件描述符（最大文件打开数）消耗殆尽；
* 当你创建一个新进程，而父进程不等待（`wait`）子进程，也不在子进程结束后收集它的终止状态，它会变为一个僵尸进程；
* 僵尸通常都会吃东西，在我们的例子中，僵尸进程会吃掉资源。如果你的服务器不管僵尸进程，它最终会消耗掉所有的可用进程（最大用户进程数）；
* 你不能杀死（`kill`）僵尸进程，你需要等待（`wait`）它。

### 如何处理僵尸进程？

所以，你需要做什么来处理僵尸进程呢？你需要修改你的服务器代码，来等待（`wait`）僵尸进程，并收集它们的终止信息。你可以在代码中使用系统调用 `wait` 来完成这个任务。不幸的是，这个方法离理想目标还很远，因为在没有终止的子进程存在的情况下调用 `wait` 会导致服务器进程阻塞，这会阻碍你的服务器处理新的客户端连接请求。那么，我们有其他选择吗？嗯，有的，其中一个解决方案需要结合信号处理以及 `wait` 系统调用。

这是它的工作流程。当一个子进程退出时，内核会发送 `SIGCHLD` 信号。父进程可以设置一个信号处理器，它可以异步响应 `SIGCHLD` 信号，并在信号响应函数中等待（`wait`）子进程收集终止信息，从而阻止了僵尸进程的存在。

顺便说一下，异步事件意味着父进程无法提前知道事件的发生时间。

修改你的服务器代码，设置一个 `SIGCHLD` 信号处理器，在信号处理器中等待（`wait`）终止的子进程。修改后的代码如下（[webserver3e.py](https://github.com/rspivak/lsbaws/blob/master/part3/webserver3e.py)）：

```
#######################################################
# 并发服务器 - webserver3e.py                          #
#                                                     #
# 使用 Python 2.7.9 或 3.4                             #
# 在 Ubuntu 14.04 及 Mac OS X 环境下测试通过            #
#######################################################
import os
import signal
import socket
import time

SERVER_ADDRESS = (HOST, PORT) = '', 8888
REQUEST_QUEUE_SIZE = 5

def grim_reaper(signum, frame):
    pid, status = os.wait()
    print(
        'Child {pid} terminated with status {status}'
        '\n'.format(pid=pid, status=status)
    )

def handle_request(client_connection):
    request = client_connection.recv(1024)
    print(request.decode())
    http_response = b"""\
HTTP/1.1 200 OK

Hello, World!
"""
    client_connection.sendall(http_response)
    ### 挂起进程，来允许父进程完成循环，并在 "accept" 处阻塞
    time.sleep(3)

def serve_forever():
    listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    listen_socket.bind(SERVER_ADDRESS)
    listen_socket.listen(REQUEST_QUEUE_SIZE)
    print('Serving HTTP on port {port} ...'.format(port=PORT))

    signal.signal(signal.SIGCHLD, grim_reaper)

    while True:
        client_connection, client_address = listen_socket.accept()
        pid = os.fork()
        if pid == 0:  ### 子进程
            listen_socket.close()  ### 关闭子进程中多余的套接字
            handle_request(client_connection)
            client_connection.close()
            os._exit(0)
        else:  ### 父进程
            client_connection.close()

if __name__ == '__main__':
    serve_forever()

```

运行服务器：

```
$ python webserver3e.py

```

使用你的老朋友——`curl` 命令来向修改后的并发服务器发送一个请求：

```
$ curl http://localhost:8888/hello

```

再来看看服务器：

刚刚发生了什么？`accept` 调用失败了，错误信息为 `EINTR`。

当子进程退出并触发 `SIGCHLD` 事件时，父进程的 `accept` 调用被阻塞了，系统转去运行信号处理器，当信号处理函数完成时，`accept` 系统调用被打断：

别担心，这个问题很好解决。你只需要重新运行 `accept` 系统调用即可。这是修改后的服务器代码 [webserver3f.py](https://github.com/rspivak/lsbaws/blob/master/part3/webserver3f.py)，它可以解决这个问题：

```
#######################################################
# 并发服务器 - webserver3f.py                          #
#                                                     #
# 使用 Python 2.7.9 或 3.4                             #
# 在 Ubuntu 14.04 及 Mac OS X 环境下测试通过            #
#######################################################
import errno
import os
import signal
import socket

SERVER_ADDRESS = (HOST, PORT) = '', 8888
REQUEST_QUEUE_SIZE = 1024

def grim_reaper(signum, frame):
    pid, status = os.wait()

def handle_request(client_connection):
    request = client_connection.recv(1024)
    print(request.decode())
    http_response = b"""\
HTTP/1.1 200 OK

Hello, World!
"""
    client_connection.sendall(http_response)

def serve_forever():
    listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    listen_socket.bind(SERVER_ADDRESS)
    listen_socket.listen(REQUEST_QUEUE_SIZE)
    print('Serving HTTP on port {port} ...'.format(port=PORT))

    signal.signal(signal.SIGCHLD, grim_reaper)

    while True:
        try:
            client_connection, client_address = listen_socket.accept()
        except IOError as e:
            code, msg = e.args
            ### 若 'accept' 被打断，那么重启它
            if code == errno.EINTR:
                continue
            else:
                raise

        pid = os.fork()
        if pid == 0:  ### 子进程
            listen_socket.close()  ### 关闭子进程中多余的描述符
            handle_request(client_connection)
            client_connection.close()
            os._exit(0)
        else:  ### 父进程
            client_connection.close()  ### 关闭父进程中多余的描述符，继续下一轮循环

if __name__ == '__main__':
    serve_forever()

```

运行更新后的服务器 `webserver3f.py`：

```
$ python webserver3f.py

```

用 `curl` 来向更新后的并发服务器发送一个请求：

```
$ curl http://localhost:8888/hello

```

看到了吗？没有 EINTR 异常出现了。现在检查一下，确保没有僵尸进程存活，调用 `wait` 函数的 `SIGCHLD` 信号处理器能够正常处理被终止的子进程。我们只需使用 `ps` 命令，然后看看现在没有处于 `Z+` 状态（或名字包含 `<defunct>` ）的 Python 进程就好了。很棒！僵尸进程没有了，我们很安心。

* 如果你创建了一个子进程，但是不等待它，它就会变成一个僵尸进程；
* 使用 `SIGCHLD` 信号处理器可以异步地等待子进程终止，并收集其终止状态；
* 当使用事件处理器时，你需要牢记，系统调用可能会被打断，所以你需要处理这种情况发生时带来的异常。

#### 正确处理 SIGCHLD 信号

好的，一切顺利。是不是没问题了？额，几乎是。重新尝试运行 `webserver3f.py` 但我们这次不会只发送一个请求，而是同步创建 128 个连接：

```
$ python client3.py --max-clients 128

```

现在再次运行 `ps` 命令：

```
$ ps auxw | grep -i python | grep -v grep

```

看到了吗？天啊，僵尸进程又出来了！

这回怎么回事？当你同时运行 128 个客户端，建立 128 个连接时，服务器的子进程几乎会在同一时间处理好你的请求，然后退出。这会导致非常多的 `SIGCHLD` 信号被发送到父进程。问题在于，这些信号不会存储在队列中，所以你的服务器进程会错过很多信号，这也就导致了几个僵尸进程处于无主状态：

这个问题的解决方案依然是设置 `SIGCHLD` 事件处理器。但我们这次将会用 `WNOHANG` 参数循环调用 `waitpid` 来替代 `wait`，以保证所有处于终止状态的子进程都会被处理。下面是修改后的代码，[webserver3g.py](https://github.com/rspivak/lsbaws/blob/master/part3/webserver3g.py)：

```
#######################################################
# 并发服务器 - webserver3g.py                          #
#                                                     #
# 使用 Python 2.7.9 或 3.4                             #
# 在 Ubuntu 14.04 及 Mac OS X 环境下测试通过            #
#######################################################
import errno
import os
import signal
import socket

SERVER_ADDRESS = (HOST, PORT) = '', 8888
REQUEST_QUEUE_SIZE = 1024

def grim_reaper(signum, frame):
    while True:
        try:
            pid, status = os.waitpid(
                -1,          ### 等待所有子进程
                 os.WNOHANG  ### 无终止进程时，不阻塞进程，并抛出 EWOULDBLOCK 错误
            )
        except OSError:
            return

        if pid == 0:  ### 没有僵尸进程存在了
            return

def handle_request(client_connection):
    request = client_connection.recv(1024)
    print(request.decode())
    http_response = b"""\
HTTP/1.1 200 OK

Hello, World!
"""
    client_connection.sendall(http_response)

def serve_forever():
    listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    listen_socket.bind(SERVER_ADDRESS)
    listen_socket.listen(REQUEST_QUEUE_SIZE)
    print('Serving HTTP on port {port} ...'.format(port=PORT))

    signal.signal(signal.SIGCHLD, grim_reaper)

    while True:
        try:
            client_connection, client_address = listen_socket.accept()
        except IOError as e:
            code, msg = e.args
            ### 若 'accept' 被打断，那么重启它
            if code == errno.EINTR:
                continue
            else:
                raise

        pid = os.fork()
        if pid == 0:  ### 子进程
            listen_socket.close()  ### 关闭子进程中多余的描述符
            handle_request(client_connection)
            client_connection.close()
            os._exit(0)
        else:  ### 父进程
            client_connection.close()  ### 关闭父进程中多余的描述符，继续下一轮循环

if __name__ == '__main__':
    serve_forever()

```

运行服务器：

```
$ python webserver3g.py

```

使用测试客户端 `client3.py`：

```
$ python client3.py --max-clients 128

```

现在来查看一下，确保没有僵尸进程存在。耶！没有僵尸的生活真美好 `^_^`。

### 大功告成

恭喜！你刚刚经历了一段很长的旅程，我希望你能够喜欢它。现在你拥有了自己的简易并发服务器，并且这段代码能够为你在继续研究生产级 Web 服务器的路上奠定基础。

我将会留一个作业：你需要将第二部分中的 WSGI 服务器升级，将它改造为一个并发服务器。你可以在[这里](https://github.com/rspivak/lsbaws/blob/master/part3/webserver3h.py)找到更改后的代码。但是，当你实现了自己的版本之后，你才应该来看我的代码。你已经拥有了实现这个服务器所需的所有信息。所以，快去实现它吧 `^_^`。

然后要做什么呢？乔希·比林斯说过：

>
> “就像一枚邮票一样——专注于一件事，不达目的不罢休。”
>
>
>

开始学习基本知识。回顾你已经学过的知识。然后一步一步深入。

>
> “如果你只学会了方法，你将会被这些方法所困。但如果你学会了原理，那你就能发明出新的方法。”——拉尔夫·沃尔多·爱默生
>
>
> “有道无术，术尚可求也，有术无道，止于术”——中国古代也有这样的话，LCTT 译注
>
>
>

下面是一份书单，我从这些书中提炼出了这篇文章所需的素材。他们能助你在我刚刚所述的几个方面中发掘出兼具深度和广度的知识。我极力推荐你们去搞到这几本书看看：从你的朋友那里借，在当地的图书馆中阅读，或者直接在亚马逊上把它买回来。下面是我的典藏秘籍：

1. [《UNIX 网络编程 卷1：套接字联网 API （第3版）》](http://www.epubit.com.cn/book/details/1692)
2. [《UNIX 环境高级编程（第3版）》](http://www.epubit.com.cn/book/details/1625)
3. [《Linux/UNIX 系统编程手册》](http://www.epubit.com.cn/book/details/1432)
4. [《TCP/IP 详解 卷1：协议（第2版）](http://www.epubit.com.cn/book/details/4232)
5. [《信号系统简明手册 (第二版): 并发控制深入浅出及常见错误》](http://www.amazon.com/gp/product/1441418687/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1441418687&linkCode=as2&tag=russblo0b-20&linkId=QFOAWARN62OWTWUG)，这本书也可以从[作者的个人网站](http://greenteapress.com/semaphores/)中免费下载到。

顺便，我在撰写一本名为《搭个 Web 服务器：从头开始》的书。这本书讲解了如何从头开始编写一个基本的 Web 服务器，里面包含本文中没有的更多细节。订阅[原文下方的邮件列表](https://ruslanspivak.com/lsbaws-part1/)，你就可以获取到这本书的最新进展，以及发布日期。

---

via: <https://ruslanspivak.com/lsbaws-part3/>

作者：[Ruslan](https://github.com/rspivak/) 译者：[StdioA](https://github.com/StdioA) 校对：[wxy](https://github.com/wxy)

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


地下城与码农：点亮你的技能树

Summary: 作为专业人员，我们不少人喜欢玩角色扮演游戏，比如 D&D 这样的，那么有没有想过把你在 Web 前端方面的技能、经验也像游戏里面一样的技能树一样点亮呢？记得回来分享你的链接。

第一层全部点满，第二层点不到。我适合干什么

适合继续学习。

内容貌似为web端，移动端的怎么办？

并没有考虑移动端，这东西又不是给一般人用的，使用频率也低，Web端就可以了

如果想web端进军的话，倒是不错的学习之路。你这么看？

# 地下城与码农：点亮你的技能树

作为专业人员，我们不少人喜欢玩角色扮演游戏，比如 D&D 这样的，那么有没有想过把你在 Web 前端方面的技能、经验也像游戏里面一样的技能树一样点亮呢？

今天给大家介绍的就是这样的一个有趣的网站：[地下城与码农](http://www.dungeonsanddevelopers.com/)（Dungeons aNd Developers）。

题图就是老王自己点亮的技能树。

啊哈，老王自己都不知道已经懂这么多了。是不是也想试试？

那么就点此链接吧： <http://www.dungeonsanddevelopers.com/> ，会看到如下界面：

点击“Open the talent tree »”，即可看到一个空白的技能树，虽然是英文的，不过这应该难不倒你。

英雄！去点亮你的技能树吧，你会回来分享你的链接的。

当然，作为专业人员，只是这样点一点，似乎并不满足，那么我们来探究一下背后所用的技术吧。

这个网站是 352 公司在内部竞赛中产生的，除了精美的仿游戏界面外，采用了 [jQuery](http://www.jquery.com/) 和 [Knockout](http://www.knockoutjs.com/) 来构建的。jQuery 大家已经熟知了，而 [Knockout](http://www.knockoutjs.com/) 可能有的人不太了解，其实它也不太新了，但是它的特性正好用在这个项目上：

>
> Knockout 是一款很优秀的 JavaScript 库，它可以帮助你仅使用一个清晰整洁的底层数据模型，即可创建一个富文本且具有良好的显示和编辑功能的用户界面。任何时候你的局部 UI 内容需要自动更新（比如：依赖于用户行为的改变或者外部的数据源发生变化），Knockout 都可以很简单的帮你实现，并且非常易于维护。
>
>
>

是不是也想自己搭建一个呢？


内容安全策略（CSP），防御 XSS 攻击的好助手

Summary: CSP 对你的用户来说是尤其重要的：他们在你的网站上不再需要遭受任何的未经请求的脚本，内容或 XSS 的威胁了。对于网站维护者来说 CSP 最重要的优势就是可感知。如果你对图片来源设置了严格的规则，这时一个脚本小子尝试在你的网站上插入一张未授权来源的图片，那么这张图片就会被禁止，并且你会在第一时间收到提醒。

# 内容安全策略（CSP），防御 XSS 攻击的好助手

很久之前，我的个人网站被攻击了。我不知道它是如何发生的，但它确实发生了。幸运的是，攻击带来的破坏是很小的：一小段 JavaScript 被注入到了某些页面的底部。我更新了 FTP 和其它的口令，清理了一些文件，事情就这样结束了。

有一点使我很恼火：在当时，还没有一种简便的方案能够使我知道那里有问题，更重要的是能够保护网站的访客不被这段恼人的代码所扰。

现在有一种方案出现了，这种技术在上述两方面都十分的成功。它就是<ruby> 内容安全策略 <rp>  （ </rp> <rt>  content security policy </rt> <rp>  ） </rp></ruby>（CSP）。

### 什么是 CSP?

其核心思想十分简单：网站通过发送一个 CSP 头部，来告诉浏览器什么是被授权执行的与什么是需要被禁止的。

这里有一个 PHP 的例子：

```
<?php
    header("Content-Security-Policy: <your directives>");
?>

```

#### 一些指令

你可以定义一些全局规则或者定义一些涉及某一类资源的规则：

```
default-src 'self' ;
     # self = 同端口，同域名，同协议 => 允许

```

基础参数是 `default-src`：如果没有为某一类资源设置指令规则，那么浏览器就会使用这个默认参数值。

```
script-src 'self' www.google-analytics.com ;
     # 来自这些域名的 JS 文件 => 允许

```

在这个例子中，我们已经授权了 www.google-analytics.com 这个域名来源的 JavaScript 文件使用到我们的网站上。我们也添加了 `'self'` 这个关键词；如果我们通过 `script-src` 来重新设置其它的规则指令，它将会覆盖 `default-src` 规则。

如果没有指明协议（scheme）或端口，它就会强制选择与当前页面相同的协议或端口。这样做防止了混合内容（LCTT 译注：混合内容指 HTTPS 页面中也有非 HTTPS 资源，可参见： <https://developer.mozilla.org/zh-CN/docs/Security/MixedContent> ）。如果页面是 https://example.com，那么你将无法加载 http://www.google-analytics.com/file.js 因为它已经被禁止了（协议不匹配）。然而，有一个例外就是协议的提升是被允许的。如果 http://example.com 尝试加载 https://www.google-analytics.com/file.js，接着协议或端口允许被更改以便协议的提升。

```
style-src 'self' data: ;
     # Data-Uri 嵌入 CSS => 允许

```

在这个例子中，关键词 `data:` 授权了在 CSS 文件中 data 内嵌内容。

在 CSP 1 规范下，你也可以设置如下规则：

* `img-src` 有效的图片来源
* `connect-src` 应用于 XMLHttpRequest（AJAX），WebSocket 或 EventSource
* `font-src` 有效的字体来源
* `object-src` 有效的插件来源（例如，`<object>`，`<embed>`，`<applet>`）
* `media-src` 有效的 `<audio>` 和 `<video>` 来源

CSP 2 规范包含了如下规则：

* `child-src` 有效的 web workers 和 元素来源，如 `<frame>` 和 `<iframe>` （这个指令用来替代 CSP 1 中废弃了的 `frame-src` 指令）
* `form-action` 可以作为 HTML `<form>` 的 action 的有效来源
* `frame-ancestors` 使用 `<frame>`，`<iframe>`，`<object>`，`<embed>` 或 `<applet>` 内嵌资源的有效来源
* `upgrade-insecure-requests` 命令用户代理来重写 URL 协议，将 HTTP 改到 HTTPS （为一些需要重写大量陈旧 URL 的网站提供了方便）。

为了更好的向后兼容一些废弃的属性，你可以简单的复制当前指令的内容同时为那个废弃的指令创建一个相同的副本。例如，你可以复制 `child-src` 的内容同时在 `frame-src` 中添加一份相同的副本。

CSP 2 允许你添加路径到白名单中（CSP 1 只允许域名被添加到白名单中）。因此，相较于将整个 www.foo.com 域添加到白名单，你可以通过添加 www.foo.com/some/folder 这样的路径到白名单中来作更多的限制。这个需要浏览器中 CSP 2 的支持，但它很明显更安全。

#### 一个例子

我为 Web 2015 巴黎大会上我的演讲 “[CSP in Action](https://rocssti.net/en/example-csp-paris-web2015)”制作了一个简单的例子。

在没有 CSP 的情况下，页面展示如下图所示：

不是十分优美。要是我们启用了如下的 CSP 指令又会怎样呢？

```
<?php
    header("Content-Security-Policy:
      default-src 'self' ;
      script-src 'self' www.google-analytics.com stats.g.doubleclick.net ;
      style-src 'self' data: ;
      img-src 'self' www.google-analytics.com stats.g.doubleclick.net data: ;
      frame-src 'self' ;");
?>

```

浏览器将会作什么呢？它会（非常严格的）在 CSP 基础规则之下应用这些指令，这意味着**任何没有在 CSP 指令中被授权允许的都将会被禁止**（“blocked” 指的是不被执行、不被显示并且不被使用在网站中）。

在 CSP 的默认设置中，内联脚本和样式是不被授权的，意味着每一个 `<script>`，`onclick` 事件属性或 `style` 属性都将会被禁止。你可以使用 `style-src 'unsafe-inline' ;` 指令来授权使用内联 CSS。

在一个支持 CSP 的现代浏览器中，上述示例看起来如下图：

发生了什么？浏览器应用了指令并且拒绝了所有没有被授权的内容。它在浏览器调试终端中发送了这些通知：

如果你依然不确定 CSP 的价值，请看一下 Aaron Gustafson 文章 “[More Proof We Don't Control Our Web Pages](https://www.aaron-gustafson.com/notebook/more-proof-we-dont-control-our-web-pages/)”。

当然，你可以使用比我们在示例中提供的更严格的指令：

* 设置 `default-src` 为 'none'
* 为每条规则指定你的设置
* 为请求的文件指定它的绝对路径
* 等

### 更多关于 CSP 的信息

#### 支持

CSP 不是一个需要复杂的配置才能正常工作的每日构建特性。CSP 1 和 2 是候选推荐标准！[浏览器可以非常完美的支持 CSP 1](http://caniuse.com/#feat=contentsecuritypolicy)。

[CSP 2 是较新的规范](http://caniuse.com/#feat=contentsecuritypolicy2)，因此对它的支持会少那么一点。

现在 CSP 3 还是一个早期草案，因此还没有被支持，但是你依然可以使用 CSP 1 和 2 来做一些重大的事。

#### 其他需要考虑的因素

CSP 被设计用来降低跨站脚本攻击（XSS）的风险，这就是不建议开启内联脚本和 `script-src` 指令的原因。Firefox 对这个问题做了很好的说明：在浏览器中，敲击 `Shift + F2` 并且键入 `security csp`，它就会向你展示指令和对应的建议。这里有一个在 Twitter 网站中应用的例子：

如果你确实需要使用内联脚本和样式的话，另一种可能就是生成一份散列值。例如，我们假定你需要使用如下的内联脚本：

```
<script>alert('Hello, world.');</script>

```

你应该在 `script-src` 指令中添加 `sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng=` 作为有效来源。这个散列值用下面的 PHP 脚本执行获得的结果：

```
<?php
    echo base64_encode(hash('sha256', "alert('Hello, world.');", true));
?>

```

我在前文中说过 CSP 被设计用来降低 XSS 风险，我还得加上“……与降低未经请求内容的风险。”伴随着 CSP 的使用，你必须**知道你内容的来源是哪里**与**它们在你的前端都作了些什么**（内联样式，等）。CSP 同时可以帮助你让贡献者、开发人员和其他人员来遵循你内容来源的规则！

现在你的问题就只是，“不错，这很好，但是我们如何在生产环境中使用它呢？”

### 如何在现实世界中使用它

想要在第一次使用 CSP 之后就失望透顶的方法就是在生产环境中测试。不要想当然的认为，“这会很简单。我的代码是完美并且相当清晰的。”不要这样作。我这样干过。相信我，这相当的蠢。

正如我之前说明的，CSP 指令由 CSP 头部来激活，这中间没有过渡阶段。你恰恰就是其中的薄弱环节。你可能会忘记授权某些东西或者遗忘了你网站中的一小段代码。CSP 不会饶恕你的疏忽。然而，CSP 的两个特性将这个问题变得相当的简单。

#### report-uri

还记得 CSP 发送到终端中的那些通知么？`report-uri` 指令可以被用来告诉浏览器发送那些通知到指定的地址。报告以 JSON 格式送出。

```
report-uri /csp-parser.php ;

```

因此，我们可以在 csp-parser.php 文件中处理有浏览器送出的数据。这里有一个由 PHP 实现的最基础的例子：

```
$data = file_get_contents('php://input');

    if ($data = json_decode($data, true)) {
     $data = json_encode(
      $data,
      JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
      );
     mail(EMAIL, SUBJECT, $data);
    }

```

这个通知将会被转换成为一封邮件。在开发过程中，你可能不会需要比这更复杂的其它东西。

对于一个生产环境（或者是一个有较多访问的开发环境），你应该使用一种比邮件更好的收集信息的方式，因为这种方式在节点上没有验证和速率限制，并且 CSP 可能变得乱哄哄的。只需想像一个会产生 100 个 CSP 通知（例如，一个从未授权来源展示图片的脚本）并且每天会被浏览 100 次的页面，那你就会每天收到 10000 个通知啊！

例如 [report-uri.io](https://report-uri.io/) 这样的服务可以用来简化你的通知管理。你也可以在 GitHub上看一些另外的使用 `report-uri` （与数据库搭配，添加一些优化，等）的简单例子。

### report-only

正如我们所见的，最大的问题就是在使用和不使用 CSP 之间没有中间地带。然而，一个名为 `report-only` 的特性会发送一个稍有不同的头部：

```
<?php
    header("Content-Security-Policy-Report-Only: <your directives>");
?>

```

总的来说，这个头部就是告诉浏览器，“表现得似乎所有的 CSP 指令都被应用了，但是不禁止任何东西。只是发送通知给自己。”这是一种相当棒的测试指令的方式，避免了任何有价值的东西被禁止的风险。

在 `report-only` 和 `report-uri` 的帮助下你可以毫无风险的测试 CSP 指令，并且可以实时的监控网站上一切与 CSP 相关的内容。这两个特性对部署和维护 CSP 来说真是相当的有用！

### 结论

#### 为什么 CSP 很酷

CSP 对你的用户来说是尤其重要的：他们在你的网站上不再需要遭受任何的未经请求的脚本，内容或 XSS 的威胁了。

对于网站维护者来说 CSP 最重要的优势就是可感知。如果你对图片来源设置了严格的规则，这时一个脚本小子尝试在你的网站上插入一张未授权来源的图片，那么这张图片就会被禁止，并且你会在第一时间收到提醒。

开发者也需要确切的知道他们的前端代码都在做些什么，CSP 可以帮助他们掌控一切。会促使他们去重构他们代码中的某些部分（避免内联函数和样式，等）并且促使他们遵循最佳实践。

#### 如何让 CSP 变得更酷

讽刺的是，CSP 在一些浏览器中过分的高效了，在和书签栏小程序一起使用时会产生一些 bug。因此，不要更新你的 CSP 指令来允许书签栏小程序。我们无法单独的责备任何一个浏览器；它们都有些问题：

* Firefox
* Chrome (Blink)
* WebKit

大多数情况下，这些 bug 都是禁止通知中的误报。所有的浏览器提供者都在努力解决这些问题，因此我们可以期待很快就会被解决。无论怎样，这都不会成为你使用 CSP 的绊脚石。

---

via: <https://www.smashingmagazine.com/2016/09/content-security-policy-your-future-best-friend/>

作者：[Nicolas Hoffmann](https://www.smashingmagazine.com/author/nicolashoffmann/) 译者：[wcnnbdk1](https://github.com/wcnnbdk1) 校对：[wxy](https://github.com/wxy)

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


React Native Ubuntu 简介

# React Native Ubuntu 简介

在 Canonical 的 Webapps 团队，我们总在寻找可以为开发者所用的 web 和 web 相关技术。我们想让每个人生活更轻松，让 web 开发者更加熟悉工具的使用，并且在 Ubuntu 上提供一个使用它们的简单途径。

我们提供对 web 应用以及创建和打包 Cordova 应用的支持，这使得在 Ubuntu 上使用任意 web 框架来创造美妙的应用体验成为可能。

其中一个可以在这些情景中使用的主流框架就是 React.js。React.js 是一个拥有声明式编程模型和强大的组件系统的 UI 框架，它主要侧重于 UI 的构建，所以你可以在你喜欢的任何地方用上它。

然而这些应用场景太广泛了，有时候你可能需要更高的性能，或者能够直接用原生 UI 组件来开发，但是在一个不太熟悉的场景中使用它可能不合时宜。如果你熟悉 React.js，那么通过 React Native 来开发可以毫不费力地将你所有现有的知识和工具迁移到完全的原生组件开发中。React Native 是 React.js 的姐妹项目，你可以用同样的方式和代码来创建一个直接使用原生组件并且拥有原生级别性能的应用，而且这就和你期待的一样轻松快捷。

我们很高兴地宣布随着我们对 HTML5 应用的支持，现在可以在 Ubuntu 平台上开发 React Native 应用了。你可以移植你现有的 iOS 或 Android 版本的 React Native 应用，或者利用你的 web 开发技能来创建一个新的应用。

你可以在 [这里](https://github.com/CanonicalLtd/react-native) 找到 React Native Ubuntu 的源代码，要开始使用时，跟随 [README-ubuntu.md](https://github.com/CanonicalLtd/react-native/blob/ubuntu/README-ubuntu.md) 的指导，并创建你的第一个应用吧。

Ubuntu 的支持包括生成软件包的功能。通过 React Native CLI，构建一个 snap 软件包只需要简单执行 `react-native package-ubuntu --snap` 这样的命令。还可以为 Ubuntu 设备构建一个 click 包，这意味着 React Native Ubuntu 应用从一开始就可以放到 Ubuntu 商店了。

在不久的将来会有很多关于在 Ubuntu 上开发一个 React Native 应用你所需要了解的东西的博文，例如创建应用、开发流程以及打包并发布到商店等等。还会有一些关于怎样开发新型的可复用的模块的信息，这些模块可以给运行时环境增加额外的功能，并且可以发布为 npm 模块。

赶快去实践一下吧，看看你能创造出些什么来。

---

via: <https://developer.ubuntu.com/en/blog/2016/08/05/introducing-react-native-ubuntu/>

作者：[Justin McPherson](https://developer.ubuntu.com/en/blog/authors/justinmcp/) 译者：[Mars Wong](https://github.com/OneNewLife) 校对：[wxy](https://github.com/wxy)

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


4 个你需要了解的容器网络工具

# 4 个你需要了解的容器网络工具

有如此之多的各种新的云计算技术、工具和技术需要我们跟进，到底从哪里开始学习是一个艰难的决定。这一系列[下一代云计算技术](https://www.linux.com/news/5-next-gen-cloud-technologies-you-should-know)的文章旨在让你快速了解新兴和快速变化领域的重大项目和产品，比如软件定义网络（SDN）、容器，以及其交叉领域：容器网络。

对于企业容器部署，容器和网络之间的关系仍然是一个挑战。容器需要网络功能来连接分布式应用程序。根据一篇最新的[企业网络星球](http://www.enterprisenetworkingplanet.com/datacenter/datacenter-blog/container-networking-challenges-for-the-enterprise.html)的文章，一部分的挑战是“以隔离的方式部署容器，在提供隔离自己容器内数据的所需功能的同时，保持有效的连接性”。

流行的容器平台 [Docker](https://docs.docker.com/engine/userguide/networking/dockernetworks/)，使用了软件定义虚拟网络来连接容器与本地网络。此外，它使用 Linux 的桥接功能和虚拟可扩展局域网（VXLAN）技术，可以在同一 Swarm 或容器集群内互相沟通。Docker 的插件架构也支持其他网络管理工具来管理容器网络，比如下面的提到的工具。

容器网络上的创新使得容器可以跨主机连接到其他容器上。这使开发人员可以在开发环境中，在一个主机上部署一个容器来运行一个应用，然后可以过渡到测试环境中，进而到生产环境中，使应用可以持续集成，敏捷开发，快速部署。

容器网络工具有助于实现容器网络的可扩展性，主要是通过：

1. 使复杂的，多主机系统能够跨多个容器主机进行分发。
2. 允许构建跨越多个公有云和私有云平台上的大量主机的容器系统。

*John Willis speaking 在 Open Networking Summit 2016.*

要获取更多信息，查看 [Docker 网络教程](https://youtu.be/Le0bEg4taak)，是由 Brent Salisbury 和 John Willis 在最近的 [Open Networking Summit (ONS)](http://events.linuxfoundation.org/events/open-networking-summit)讲演的。更多关于 ONS 的演讲内容可以在[这里](https://www.linux.com/watch-videos-from-ons2016)找到。

你应该知道的容器网络工具和项目包括下述：

* [Calico](https://www.projectcalico.org/) -- Calico 项目（源自 [Metaswitch](http://www.metaswitch.com/cloud-network-virtualization)）利用边界网关协议（BGP）和集成的云编排系统来保证虚拟机和容器之间的 IP 通信安全。
* [Flannel](https://coreos.com/blog/introducing-rudder/) -- Flannel (之前叫 rudder) 源自 [CoreOS](https://coreos.com/)，它提供了一个覆盖网络，可以作为一个现有的 SDN 解决方案的替代品。
* [Weaveworks](https://www.weave.works/) -- Weaveworks 项目管理容器的工具包括 [Weave Net](https://www.weave.works/products/weave-net/)、Weave Scope、Weave Flux。Weave Net 是一种用于构建和部署 Docker 容器的网络工具。
* [Canal](https://github.com/tigera/canal) -- 就在本周，CoreOS 和 Tigera 宣布了新的开源项目 Canal 的信息。据其声明，Canal 项目旨在结合部分 Calico 和 Flannel，“构造网络安全策略到网络架构和云管理平台之中”。

你可以通过 Linux 基金会的免费“云基础设施技术”课程来了解更多关于容器管理、软件定义网络和其他下一代云技术，这是一个在 edX 上提供的大规模公开在线课程。[课程注册目前已经开放](https://training.linuxfoundation.org/linux-courses/system-administration-training/introduction-to-cloud-infrastructure-technologies?utm_source=linuxcom&utm_medium=article&utm_campaign=cloud%20mooc%20article%201)，课程内容于 6 月开放。

---

via: <https://www.linux.com/news/4-container-networking-tools-know>

作者：[AMBER ANKERHOLZ](https://www.linux.com/users/aankerholz) 译者：[Bestony](https://github.com/Bestony) 校对：[wxy](https://github.com/wxy)

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


Terminator：一款一个窗口包含多个终端的 Linux 终端仿真器

这货很好用，虽然一般情况默认终端就能满足我，但也有手忙脚乱的时候，这时候Terminator就上场了，不过最多我只会分成四个窗口~

想问下有 T-1000 吗？

有 T-800 : https://linux.cn/article-6745-1.html

已用N年

不知道和screen/tmux相比有什么明显优势？

可以的，有空试试

用平铺wm就用不上terminator了,tmux也不错,选择蛮多的

题主，谢谢你的分享，但是我在使用的过程中遇到了一个问题：

我是下载的源码，然后运行[./setup.py install]，但是安装完成运行命令[terminator]之后，出现：

linux@ubuntu64-vm:/mnt/hgfs/vmshare/terminator-1.90$ terminator

Traceback (most recent call last):

  File "/usr/local/bin/terminator", line 23, in <module>

    import psutil

ImportError: No module named psutil

应该是少一个模块，请问应该如何解决这个问题？

再次感谢！

四个最棒了

# Terminator：一款一个窗口包含多个终端的 Linux 终端仿真器

为了通过命令行和系统互动，每一款 Linux 发行版都有一款默认的终端仿真器。但是，默认的终端应用可能不适合你。为了大幅提升你工作的速度，有好多款终端应用提供了更多的功能，可以同时执行更多的任务。这些有用的终端仿真器就包括 Terminator，这是一款 Linux 系统下支持多窗口的自由开源的终端仿真器。

### 什么是 Linux 终端仿真器

Linux 终端仿真器是一个让你和 shell 交互的程序。所有的 Linux 发行版都会自带一款 Linux 终端应用让你向 shell 传递命令。

### Terminator，一款自由开源的 Linux 终端应用

Terminator 是一款 Linux 终端模拟器，提供了你的默认的终端应用不支持的多个特性。它提供了在一个窗口创建多个终端的功能，以加快你的工作速度。除了多窗口外，它也允许你修改其它特性，例如字体、字体颜色、背景色等等。让我们看看我们如何安装它，并且如何在不同的 Linux 发行版下使用 Terminator。

### 如何在 Linux 下安装 Terminator？

#### 在基于 Ubuntu 的发行版上安装 Terminator

Terminator 在默认的 Ubuntu 仓库就可以使用。所以你不需要添加额外的 PPA。只需要使用 APT 或者“软件应用”在 Ubuntu 下直接安装。

```
sudo apt-get install terminator

```

假如你的默认的仓库中 Terminator 不可用，只需要使用源码编译 Terminator 即可。

* [下载源码](https://launchpad.net/terminator/+download)

下载 Terminator 源码并且解压到你的桌面。现在打开你的默认的终端，然后 `cd` 到解压的目录。

现在就可以使用下面的命令来安装 Terminator 了：

```
sudo ./setup.py install

```

#### 在 Fedora 及衍生的操作系统上安装 Terminator

```
dnf install terminator

```

#### 在 OpenSuse 上安装 Terminator

参见此文：[在 OPENSUSE 上安装](http://software.opensuse.org/download.html?project=home%3AKorbi123&package=terminator)。

### 如何在一个窗口使用多个终端？

安装好 Terminator 之后，你可以简单的在一个窗口打开多个终端。只需要右键点击并切分。

只要你愿意，你可以创建尽可能多的终端，只要你能管理得过来。

### 定制终端

右键点击终端，并单击属性。现在你可以定制字体、字体颜色、标题颜色和背景，还有终端字体颜色和背景。

### 结论：什么是你最喜欢的终端模拟器

Terminator 是一款先进的终端模拟器，它可以让你自定义界面。如果你还没有从你默认的终端模拟器中切换过来的话，你可以尝试一下它。我知道你将会喜欢上它。如果你正在使用其他的自由开源的终端模拟器的话，请让我们知道你最喜欢的那一款。不要忘了和你的朋友分享这篇文章。或许你的朋友正在寻找类似的东西。

---

via: <http://www.linuxandubuntu.com/home/terminator-a-linux-terminal-emulator-with-multiple-terminals-in-one-window>

作者：[linuxandubuntu](http://www.linuxandubuntu.com/home/terminator-a-linux-terminal-emulator-with-multiple-terminals-in-one-window) 译者：[yangmingming](https://github.com/yangmingming) 校对：[wxy](https://github.com/wxy)

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


当我有 6 百万时，就发布 Linux 5.0

Summary: 据 Linus Torvalds 最近的一则 Google+ 帖子，他开玩笑的说，当 Linux 项目有 6 百万个 Git 对象时就发布 Linux 5.0 内核。

改成 Linux Kernel 5.0 会更严谨些，单写 Linux 5.0 第一眼可能以为是发行版。

什么发行版... 只是那些人太片面

6 百万也是，咋一看以为是说钱。

严格来说，Linux 其实就只是内核而已，只是人们习惯把 Linux 发行版当成 Linux 罢了。

我也是，但是我又觉得600万元虽然很多，但脱瓦斯冒个泡应该很快就能筹够

我觉得那些能看成是发行版的人，可能根本都分不清发行版和内核的区别。。。

林纳斯缺钱了

未必，对于名为 Linux Lite 面向新人的发行版，确实容易让人把发行版和内核相混淆。

2.6系列有40个版本。3系列有20个版本，4系列有10个版本，按照这进度，估计5系列只有5个版本就到6系列了。

强势

小白路过

这货年薪一千万美金

可惜国内不能访问，否则肯定超过600w

# 当我有 6 百万时，就发布 Linux 5.0

据 Linus Torvalds [最近的一则 Google+ 帖子](https://plus.google.com/+LinusTorvalds/posts/hbdxizBNrYn)，他开玩笑的说，当 Linux 项目有 6 百万个 Git 对象时就发布 Linux 5.0 内核。

Linux 内核主要的里程碑版本，比如 Linux 3.0、[Linux 4.0](/article-5259-1.html) 都是发布于 Git 对象数量分别达到了 2 百万和 4 百万时，而在2016 年 10 月 8 日，当前的 Git 对象已经达到了 5 百万，所以，看起来到下一个百万数量线时，就会发布 Linux 5.0 了。

“现在我们正处于 Linux 4.0 和 5.0 中间，”Linus Torvalds 说，“换句话说： 3.0 发布于内核 Git 对象数据库有 2 百万个对象时，而 4.0 发布于 4 百万时。所以，很自然的，按数字来说，毫无疑问 5.0 应该在我们到达 6 百万对象时发布。”

<img src="content_image/album_201610_11_125327b3343qilq2twei23.jpg" alt="" title="">

正如你已经知道的， [Linux 4.8 内核已经于 2016 年 10 月 2 日发布](/article-7837-1.html)，而且由于一个[严重错误](http://lkml.iu.edu/hypermail/linux/kernel/1610.0/00878.html)，很快发布了第一个维护版本 4.8.1。这就代表目前已经进入了 Linux 4.9 内核的开发阶段，它也是下一个 LTS 分支。而就在下周 10 月 16 日，4.9 就会发布其第一个 RC 版本。

### Linux 5.0 内核的开发将于 2016 年 12 月开始

如果 Linux 4.9 内核能够正常发布，有 7 个 RC 版本的话，那它应该正式发布于 12 月 4 日。如果不幸的遇到了一些问题而延迟，那有可能要到 12 月中旬才能开启 Linux 5.0 内核的合并窗口，也许会在今年的圣诞节前发布第一个 RC 版本。

而且，如果 Linux 5.0 的开发也是常规的 7 个 RC 版本的话， 第二个 RC 版本应该出现在明年 1 月 8 日，而最后一个则会在 2017 年的 2 月中旬。不管怎么说，我们会在 2 月份见到 Linux 5.0 内核的正式发布——当然，如果 6 百万 Git 对象的线达到了的话。


如何使用 SSHFS 通过 SSH 挂载远程的 Linux 文件系统或者目录

sshfs 也是有不足的地方，不支持命令 chattr 或 lsattr 。

如果希望向远程主机追加内容，到启用了(chattr +a)的日志文件，这是我的变通解决方法。

declare exec='\su - copyist --command'

declare args="$'\ssh -fnt ${REMOTE_USER}@${REMOTE_HOST} \'echo \'${INFO}\' >> ${LOG_FILE}\''"

eval $exec $args &> /nul

/nul 是指向 /dev/null 的文件符号链接。

P.S. 其实我好想把 /nul 的 / 也给生略了，就像 Windows 那样，不过没有找到方法。

sshfs如何设置权限，才能实现只读挂载或读写挂载，

来自210.12.140.210的 QQ Browser 10.5|Windows 10 用户

# 如何使用 SSHFS 通过 SSH 挂载远程的 Linux 文件系统或者目录

写这篇文章的主要目的就是提供一步一步的指导，关于如何使用 SSHFS 通过 SSH 挂载远程的 Linux 文件系统或目录。

这篇文章对于那些无论出于什么目的，希望在他们本地的系统中挂载远程的文件系统的用户或者系统管理员有帮助。我们通过 Linux 系统中的一个安装了 SSHFS 客户端进行实际测试，并且成功的挂载了远程的文件系统。

在我们进一步安装之前，让我们了解一下 SSHFS 的相关内容，以及它是如何工作的。

<img src="content_image/album_201610_12_095124vr9cmdgilii8rrwr.png" alt="" title="">

*Sshfs 挂载远程的 Linux 文件系统或者目录*

### 什么是 SSHFS？

SSHFS（Secure SHell FileSystem）是一个客户端，可以让我们通过 SSH 文件传输协议（SFTP）挂载远程的文件系统并且在本地机器上和远程的目录和文件进行交互。

SFTP 是一种通过 SSH 协议提供文件访问、文件传输和文件管理功能的安全文件传输协议。因为 SSH 在网络中从一台电脑到另一台电脑传输文件的时候使用数据加密通道，并且 SSHFS 内置在 FUSE（用户空间的文件系统）内核模块，允许任何非特权用户在不修改内核代码的情况下创建他们自己的文件系统。

在这篇文章中，我们将会向你展示在任意 Linux 发行版上如何安装并且使用 SSHFS 客户端，在本地 Linux 机器上挂载远程的 Linux 文件系统或者目录。

#### 步骤 1：在 Linux 系统上安装 SSHFS

默认情况下，sshfs 包不存在所有的主流 Linux 发行版中，你需要在你的 Linux 系统中启用 [epel 仓库](/article-2324-1.html)，在 Yum 命令行的帮助下安装 SSHFS 及其依赖。

```
# yum install sshfs
# dnf install sshfs              【在 Fedora 22+ 发行版上】
$ sudo apt-get install sshfs     【基于 Debian/Ubuntu 的系统】

```

#### 步骤 2：创建 SSHFS 挂载目录

当你安装 SSHFS 包之后，你需要创建一个挂载点目录，在这儿你将要挂载你的远程文件系统。例如，我们在 /mnt/tecmint 下创建挂载目录。

```
# mkdir /mnt/tecmint
$ sudo mkdir /mnt/tecmint     【基于 Debian/Ubuntu 的系统】

```

#### 步骤 3：使用 SSHFS 挂载远程的文件系统

当你已经创建你的挂载点目录之后，现在使用 root 用户运行下面的命令行，在 /mnt/tecmint 目录下挂载远程的文件系统。视你的情况挂载目录可以是任何目录。

下面的命令行将会在本地的 /mnt/tecmint 目录下挂载一个叫远程的一个 /home/tecmint 目录。（不要忘了使用你的 IP 地址和挂载点替换 x.x.x.x）。

```
# sshfs tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint
$ sudo sshfs -o allow_other tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint      【基于 Debian/Ubuntu 的系统】

```

如果你的 Linux 服务器配置为基于 SSH 密钥授权，那么你将需要使用如下所示的命令行指定你的公共密钥的路径。

```
# sshfs -o IdentityFile=~/.ssh/id_rsa tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint
$ sudo sshfs -o allow_other,IdentityFile=~/.ssh/id_rsa tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint     【基于 Debian/Ubuntu 的系统】

```

#### 步骤 4：验证远程的文件系统挂载成功

如果你已经成功的运行了上面的命令并且没有任何错误，你将会看到挂载在 /mnt/tecmint 目录下的远程的文件和目录的列表

```
# cd /mnt/tecmint
# ls
[root@ tecmint]# ls
12345.jpg                       ffmpeg-php-0.6.0.tbz2                Linux                                           news-closeup.xsl     s3.jpg
cmslogs                         gmd-latest.sql.tar.bz2               Malware                                         newsletter1.html     sshdallow
epel-release-6-5.noarch.rpm     json-1.2.1                           movies_list.php                                 pollbeta.sql
ffmpeg-php-0.6.0                json-1.2.1.tgz                       my_next_artical_v2.php                          pollbeta.tar.bz2

```

#### 步骤 5：使用 df -hT 命令检查挂载点

如果你运行 df -hT命令，你将会看到远程文件系统的挂载点。

```
# df -hT

```

样本输出：

```
Filesystem                          Type        Size  Used Avail Use% Mounted on
udev                                devtmpfs    730M     0  730M   0% /dev
tmpfs                               tmpfs       150M  4.9M  145M   4% /run
/dev/sda1                           ext4         31G  5.5G   24G  19% /
tmpfs                               tmpfs       749M  216K  748M   1% /dev/shm
tmpfs                               tmpfs       5.0M  4.0K  5.0M   1% /run/lock
tmpfs                               tmpfs       749M     0  749M   0% /sys/fs/cgroup
tmpfs                               tmpfs       150M   44K  150M   1% /run/user/1000
tecmint@192.168.0.102:/home/tecmint fuse.sshfs  324G   55G  253G  18% /mnt/tecmint

```

#### 步骤 6：永久挂载远程文件系统

为了永久的挂载远程的文件系统，你需要修改一个叫 `/etc/fstab` 的文件。照着做，使用你最喜欢的编辑器打开文件。

```
# vi /etc/fstab
$ sudo vi /etc/fstab     【基于 Debian/Ubuntu 的系统】

```

移动到文件的底部并且添加下面的一行，保存文件并退出。下面条目表示使用默认的设置挂载远程的文件系统。

```
sshfs#tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint fuse.sshfs defaults 0 0

```

确保服务器之间允许 [SSH 无密码登录](/article-5444-1.html)，这样系统重启之后才能自动挂载文件系统。

如果你的服务器配置为基于 SSH 密钥的认证方式，请加入如下行：

```
sshfs#tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint fuse.sshfs IdentityFile=~/.ssh/id_rsa defaults 0 0

```

接下来，你需要更新 fstab 文件使修改生效。

```
# mount -a
$ sudo mount -a   【基于 Debian/Ubuntu 的系统】

```

#### 步骤 7：卸载远程的文件系统

为了卸载远程的文件系统，只需要发出以下的命令即可。

```
# umount /mnt/tecmint

```

目前为止就这样了，如果你在挂载远程文件系统的时候遇到任何问题或者需要任何帮助，请通过评论联系我们，如果你感觉这篇文章非常有用，请分享给你的朋友们。

---

via: <http://www.tecmint.com/sshfs-mount-remote-linux-filesystem-directory-using-ssh/>

作者：[Ravi Saive](http://www.tecmint.com/author/admin/) 译者：[yangmingming](https://github.com/yangmingming) 校对：[wxy](https://github.com/wxy)

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


在电脑和安卓设备之间使用 FTP 传输文件

Summary: 每一个使用安卓设备的人都知道可以使用 USB 线连接电脑来传输文件，但是不是所有人都知道可以使用自由开源软件（FOSS 软件）通过无线连接到安卓设备。

MIUI自带FTP服务

FTP sever我用Andro File Server（幾百KB size，迷你小巧，支持FTP和SSH，貌似已在Google Play Store下架？），FTP client我用nautilus

好麻烦，有数据线就够了啊，或者安装个邮箱就完了

这个现在就是这样使用，不过软件不是用这样的。。。。。

es文件管理器 打开远程服务 自己就建立了一个ftp了，然后你在linux桌面的文件管理器里面输入ft://xxxx（手机上那个网址）就可以了……<br />
话说一般传文件要么用telegram/pushbullet，要么用蓝牙（小文件） 方便啊……

# 在电脑和安卓设备之间使用 FTP 传输文件

每一个使用安卓设备的人都知道可以[使用 USB 线连接电脑来传输文件](https://itsfoss.com/how-to-connect-kindle-fire-hd-with-ubuntu-12-10/)，但是不是所有人都知道可以使用自由开源软件（FOSS 软件）通过无线连接到安卓设备。

我知道，这并非最简易的方式，但这样做的确很有趣而且感觉很极客。所有，如果你有一些 DIY 精神，让我为你展示如何在 Linux 和 安卓设备之间使用 FTP 来传输文件。

### 第一步：安装必要的软件

为了连接你的安卓设备，至少需要：一个 FTP 服务器和一个 FTP 客户端。这两者都有不少的选择，个人比较推荐 [Filezilla](https://filezilla-project.org/) 作为客户端，因为它是开源的，而且支持多种平台。

我最喜欢的 FOSS 安卓应用商店就是 [F-Droid](https://f-droid.org/)。F-Droid 有两个非常棒的 FTP 服务端应用：[primitive ftpd](https://f-droid.org/repository/browse/?fdfilter=ftp&fdid=org.primftpd) 和 [FTP Server (Free)](https://f-droid.org/repository/browse/?fdfilter=ftp&fdid=be.ppareit.swiftp_free)。这两者都只能运行在安卓 4.0 或者更高的版本中。本文我会集中介绍 primitive ftpd，如果使用 FTP Sever (free) 也是一样的步骤。

### 第二步：熟悉 FTP 服务器

安装好 FTP 客户端和服务端之后，就是设置两者之间的连接了。先从安卓设备的 FTP 服务端开始，首先，通过应用启动器打开 primitive ftpd。

打开应用之后，你将看到一大串的数字。不用紧张，你可以看到服务端已经分配到一个 IP 地址 (本文是 192.168.1.131)。往下看，将看到 FTP 和 SFTP 服务 (SFTP 是一个截然不同的协议，它通过 SSH 进行连接) 都是还未运行的。再往下就是用户名，本文设置为 user。

在屏幕顶端有两个按钮，一个用于开启 FTP 服务，另一个则是设置 FTP 服务。启动服务是不言自明的。

在设置菜单中，可以改变服务器的用户名、访问密码以及所使用的端口。并且你可以设置当服务器激活是阻止待机、服务开机自启或者变更主题。

### 第三步：使用 Filezilla

现在打开对应你所用系统的 Filezilla。使用 Filezilla 有两种方法：在需要传输文件时输入 IP、用户名、密码和端口，或者在 Filezilla 中预先保存这些信息（值得注意的是：基本上，每次你打开 FTP 服务时，IP 都会不同的，所用需要更新保存在 Filezilla 中信息）。我会展示这两种方法。

如果你想要手动输入，直接在 Filezilla 窗口上方输入必要的信息然后点击“快速连接”即可。

需要预先存储信息的话，点击文件菜单下方的按钮，这会打开“站点管理器”对话框，填写你想要保存的信息即可。通常，我设置“登录类型”为“询问密码”，安全为上。如果使用的是 FTP，保存默认选项即可；但如果使用的是 SFTP，必须在协议下拉选项框中进行协议的选择。

点击连接，输入密码即可。你会看到一个连接新主机的警告，这是可以在此验证屏幕上显示的“指纹信息”是否与 Primitive FTPD 上的一致。如果一致 (也应该是一致的)，点击确认添加都已知主机列表，之后就不会出现该警告了。

### 第四步：传输文件

现在，你会看到两个框，一个是“本地站点”，一个是“远程站点”，对应的呈现了你电脑和安装设备上的目录和文件信息。然后你就可以在电脑端浏览和传输文件到你的安卓设备上了。个人建议上传文件到你的 Download 文件夹，以便于文件跟踪管理。你可以右击某个文件，在弹出的菜单中选择上传或者下载以及移动操作，或者简单双击也行。

### 第五步：停止服务

当你完成文件的传输之后，你需要做得就是停止安卓设备上的 FTP 服务和关闭 Filezilla，如此简单，不是吗？

### 结论

我相信会有人指出，FTP 并不安全。而我认为，本例不需要考虑这个问题，因为连接时间一般很短。并且多数情况下，都是在家庭私有网络环境中进行操作。

这就是我在 Linux 和安卓设备间最喜欢使用的方法。

觉得有用吗？或有又没觉得推荐的相似软件？请在评论中告诉我们。

如果觉得此文有用，请花几分钟分享到你常用的社交站点中。

---

via: <https://itsfoss.com/use-ftp-linux-android/>

作者：[John Paul](https://itsfoss.com/author/john/) 译者：[GHLandy](https://github.com/GHLandy) 校对：[wxy](https://github.com/wxy)

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


Windows 的 Linux 子系统之 Arch Linux

使用Linux的原因很大程度是因为windows无法满足需要才用了，这么弄有点无语了

linux兼容，是因为要用到一些linux bash下的命令，但这样感觉没有必要。很多时候真想装linux，不如弄一个虚拟机。

而bash命令行也有mysys和cygwin可以用。

真的感觉这么做有点多余

来自江苏南京的 Internet Explorer 11.0|Windows 7 用户

优势是原生。不是所有包都有对应的正对 Windows 重编译版。有这个子系统，已有包可以直接拿来用，方便。

我感觉这不是个好的想法和项目，它会造成Windows更大范围的垄断。

相反，继续发展Wine，弄个Widows on Linux，1024MB内存、5GB硬盘就可以运行Windows，那就可以不用完整安装Windows了。

发这种评论时，建议还是注意下细节。不然以评论的内容，结合显示出的 user-agent 看，会有些怪。

“就可以运行Windows”笔误了，应是程序，wine 的功能本来就是这个。但是限制是有的，一有系统 Windows 本身的复杂度，还有 Windows 应用自身的限制，比如拒绝以 wine 方式运行。还有现在微软主推 Windows 10 Store+UWP 方式，单单一个 wine 不好弄，因为 Store 这块不好模拟，既有技术上的限制，也有法律上的限制。

linux 吧很早前就有人这么做了

我不要这个，我要的是linux能完美兼容windows应用，不用windows就是嫌其慢，这样搞，我～～～

好消息，必须支持，一直都在用archlinux

微软做的不赖，在linux桌面的菊花上来一枪。以后给新手推荐linux桌面，除了推荐win10其它都特么害人！终于可以宣布“Linux占领了桌面系统”，biangbiang哒！希望win10永久终结其它linux桌面，说真的我已经受够他们了。

哈哈

没有深度探索wsl运行机制，稳定性。只能算是推广文，或神话文

来自广东广州的 Chrome Mobile 49.0|Android 6.0 用户

0.6版本在哪里呢，没有看到可以使用的版本下载呦

来自浙江杭州的 Chrome Mobile 45.0|Android 5.1 用户

我怎么觉得是Linux已经攻入windows内部，windows离终结不远了

好像是这个https://github.com/turbo/alwsl/releases

额...这个改进能吸引到多少用户呢？在那个自动升级机制以及不能卸载的 Xbox 等等问题存在的情况下。

萝卜白菜各有所爱

挺期待的,arch

# Windows 的 Linux 子系统之 Arch Linux

Ubuntu 的制造商 [Canonical](http://www.canonical.com/) 早已和微软进行合作，让我们体验了极具争议的 [Bash on Windows](https://itsfoss.com/bash-on-windows/)。外界对此也是褒贬不一，许多 Linux 重度用户则是质疑其是否有用，以及更进一步认为 [Bash on Windows 是一个安全隐患](https://itsfoss.com/linux-bash-windows-security/)。

Unix 的 Bash 是通过 WSL (<ruby> Windows 的 Linux 子系统 <rp>  （ </rp> <rt>  Windows Subsystem for Linux </rt> <rp>  ） </rp></ruby>) 特性移植到了 Windows 中。早先，我们已经展示过 [安装 Bash 到 Windows](/article-7209-1.html)。

Canonical 和微软合作的 Bash on Windows 也仅仅是 Ubuntu 的命令行而已，并非是正规的图形用户界面。

不过，有个好现象是 Linux 爱好者开始在上面投入时间和精力，他们在 WSL 做到的成绩甚至让最初的开发者都吃惊，“等等，这真的可以吗？”。

这个正在逐步实现之中。

没错，上图所示就是运行在 Windows 中的 Ubuntu Unity 桌面。一位名为 Pablo Gonzalez （GitHub ID 为 [Guerra24](https://github.com/Guerra24) ）的程序员将这个完美实现了。随着这个实现，他向我们展示了 WSL 已经超越了当初构想之时的功能。

如果现在可以在 Windows 子系统之中运行 Ubuntu Unity，那么运行其他的 Linux 发行版还会远吗？

### Arch Linux 版的 Bash on Windows

在 WSL 本地运行完整的 Linux发行版，迟早是要实现的。而我最希望的就是 [Arch Linux](https://www.archlinux.org/) （[Antergos](https://itsfoss.com/tag/antergos/) 爱好者点击此处）。

Hold 住，Hold 住，该项目目前还在测试中。它由“mintxomat”在 GitHub 上开发的，最新为 0.6 版本。第一个稳定版将在今年的 12 月底发布。

那么，该项目的出台会有什么不同呢？

你可能早就知道，WSL 仅在 Windows 10 中可用。但是 Windows 的 Linux 子系统之 Arch Linux (AWSL) 早已成功的运行在 Windows 7、Windows 8、Windows 8.1 和 Windows Server 2012(R2)，当然还有 Windows 10。

我靠，他们是怎么做到的？！

其开发者曾说，AWSL 协议抽象归纳了不同 Windows 版本的各种框架。所以，当 AWSL 发布 1.0 应该会取得更大的成绩。如我们之前提到的移植性，该项目会先向所有 Windows 版本推出 Bash on Windows。

该项目很有雄心，并且很有看头。如果等不及 12 月底的稳定版，你可以先行尝试其测试版。但要记住，目前还是开发者预览版，此刻并不稳定。但是，我们什么时候停止过折腾的脚步？

你也可到 GitHub 查看此项目的进度：[Arch on Windows Subsystem](https://github.com/turbo/alwsl)

分享本文，以便大家都知道 Arch Linux 即将登陆 Windows 子系统。同时，也告诉我们，你希望 WSL 中有什么发行版。

---

via: <https://itsfoss.com/arch-linux-windows-subsystem/>

作者：[Aquil Roshan][a] 译者：[GHLandy](https://github.com/GHLandy) 校对：[wxy](https://github.com/wxy)

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