分类 技术 下的文章

一个小问题:你每天做什么事?当然了,好多事情,但是我可以指出一件事,你几乎每天(如果不是每天)都会用 Google 搜索,我说的对吗?(LCTT 译注:Google 是啥?/cry )

现在,如果你是一位 Linux 用户(我猜你也是),这里有另外一个问题:如果你甚至不用离开终端就可以进行 Google 搜索那岂不是相当棒?甚至不用打开一个浏览器窗口?

如果你是一位类 *nix 系统的狂热者而且也是喜欢终端界面的人,我知道你的答案是肯定的,而且我认为,接下来你也将喜欢上我今天将要介绍的这个漂亮的小工具。它被称做 Googler。

Googler:在你 linux 终端下的 google

Googler 是一个简单的命令行工具,它用于直接在命令行窗口中进行 google 搜索,Googler 主要支持三种类型的 Google 搜索:

  • Google 搜索:简单的 Google 搜索,和在 Google 主页搜索是等效的。
  • Google 新闻搜索:Google 新闻搜索,和在 Google News 中的搜索一样。
  • Google 站点搜索:Google 从一个特定的网站搜索结果。

Googler 用标题、链接和网页摘要来显示搜索结果。搜索出来的结果可以仅通过两个按键就可以在浏览器里面直接打开。

在 Ubuntu 下安装 Googler

先让我们进行软件的安装。

首先确保你的 python 版本大于等于 3.3,可以用以下命令查看。

python3 --version

如果不是的话,就更新一下。Googler 要求 python 版本 3.3 及以上运行。

虽然 Googler 现在还不能在 Ununtu 的软件库中找到,我们可以很容易地从 GitHub 仓库中安装它。我们需要做的就是运行以下命令:

cd /tmp
git clone https://github.com/jarun/googler.git
cd googler
sudo make install
cd auto-completion/bash/
sudo cp googler-completion.bash /etc/bash_completion.d/

这样 Googler 就带着命令自动完成特性安装完毕了。

特点 & 基本用法

如果我们快速浏览它所有的特点,我们会发现 Googler 实际上是一个十分强大的工具,它的一些主要特点就是:

交互界面

在终端下运行以下命令:

googler

交互界面就会被打开,Googler 的开发者 Arun Prakash Jana 称之为 全向提示符 omniprompt ,你可以输入 ? 去寻找可用的命令参数:

在提示符处,输入任何搜索词汇关键字去开始搜索,然后你可以输入n或者p导航到搜索结果的后一页和前一页。

要在浏览器窗口中打开搜索结果,直接输入搜索结果的编号,或者你可以输入 o 命令来打开这个搜索网页。

新闻搜索

如果你想去搜索新闻,直接以N参数启动 Googler:

googler -N

随后的搜索将会从 Google News 抓取结果。

站点搜索

如果你想从某个特定的站点进行搜索,以w 域名参数启动 Googler:

googler -w itsfoss.com

随后的搜索会只从这个博客中抓取结果!

手册页

运行以下命令去查看 Googler 的带着各种用例的手册页:

man googler

指定国家/地区的 Google 搜索引擎

googler -c in "hello world"

上面的示例命令将会开始从 Google 的印度域名搜索结果(in 代表印度)

还支持:

  • 通过时间和语言偏好来过滤搜索结果
  • 支持 Google 查询关键字,例如:site:example.com 或者 filetype:pdf 等等
  • 支持 HTTPS 代理
  • Shell 命令自动补全
  • 禁用自动拼写纠正

这里还有更多特性。你可以用 Googler 去满足你的需要。

Googler 也可以和一些基于文本的浏览器整合在一起(例如:elinkslinkslynx、w3m 等),所以你甚至都不用离开终端去浏览网页。在 Googler 的 GitHub 项目页可以找到指导。

如果你想看一下 Googler 不同的特性的视频演示,方便的话你可以查看 GitHub 项目页附带的终端记录演示页: jarun/googler v2.7 quick demo

对于 Googler 的看法?

尽管 googler 可能并不是对每个人都是必要和渴望的,对于一些不想打开浏览器进行 google 搜索或者就是想泡在终端窗口里面的人来说,这是一个很棒的工具。你认为呢?


via: https://itsfoss.com/review-googler-linux/

作者:Munif Tanjim 译者:LinuxBars 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Pentbox 是一个包含了许多可以使渗透测试工作变得简单流程化的工具的安全套件。它是用 Ruby 编写并且面向 GNU / Linux,同时也支持 Windows、MacOS 和其它任何安装有 Ruby 的系统。在这篇短文中我们将讲解如何在 Kali Linux 环境下设置蜜罐。如果你还不知道什么是 蜜罐 honeypot ,“蜜罐是一种计算机安全机制,其设置用来发现、转移、或者以某种方式,抵消对信息系统的非授权尝试。”

下载 Pentbox:

在你的终端中简单的键入下面的命令来下载 pentbox-1.8。

root@kali:~# wget http://downloads.sourceforge.net/project/pentbox18realised/pentbox-1.8.tar.gz

解压 pentbox 文件

使用如下命令解压文件:

root@kali:~# tar -zxvf pentbox-1.8.tar.gz

运行 pentbox 的 ruby 脚本

改变目录到 pentbox 文件夹:

root@kali:~# cd pentbox-1.8/

使用下面的命令来运行 pentbox:

root@kali:~# ./pentbox.rb

设置一个蜜罐

使用选项 2 (Network Tools) 然后是其中的选项 3 (Honeypot)。

完成让我们执行首次测试,选择其中的选项 1 (Fast Auto Configuration)

这样就在 80 端口上开启了一个蜜罐。打开浏览器并且打开链接 http://192.168.160.128 (这里的 192.168.160.128 是你自己的 IP 地址。)你应该会看到一个 Access denied 的报错。

并且在你的终端应该会看到 “HONEYPOT ACTIVATED ON PORT 80” 和跟着的 “INTRUSION ATTEMPT DETECTED”。

现在,如果你在同一步选择了选项 2 (Manual Configuration), 你应该看见更多的其它选项:

执行相同的步骤但是这次选择 22 端口 (SSH 端口)。接着在你家里的路由器上做一个端口转发,将外部的 22 端口转发到这台机器的 22 端口上。或者,把这个蜜罐设置在你的云端服务器的一个 VPS 上。

你将会被有如此多的机器在持续不断地扫描着 SSH 端口而震惊。 你知道你接着应该干什么么? 你应该黑回它们去!桀桀桀!

如果视频是你的菜的话,这里有一个设置蜜罐的视频:


via: https://www.blackmoreops.com/2016/05/06/setup-honeypot-in-kali-linux/

作者:<blackmoreops.com> 译者:wcnnbdk1 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

旅行中备份图片 - 组件

介绍

我在很长的时间内一直在寻找一个旅行中备份图片的理想方法,把 SD 卡放进你的相机包会让你暴露在太多的风险之中:SD 卡可能丢失或者被盗,数据可能损坏或者在传输过程中失败。比较好的一个选择是复制到另外一个介质中,即使它也是个 SD 卡,并且将它放到一个比较安全的地方去,备份到远端也是一个可行的办法,但是如果去了一个没有网络的地方就不太可行了。

我理想的备份步骤需要下面的工具:

  1. 用一台 iPad pro 而不是一台笔记本。我喜欢轻装旅行,我的大部分旅程都是商务相关的(而不是拍摄休闲的),我痛恨带着个人笔记本的时候还得带着商务本。而我的 iPad 却一直带着,这就是我为什么选择它的原因。
  2. 用尽可能少的硬件设备。
  3. 设备之间的连接需要很安全。我需要在旅馆和机场使用这套设备,所以设备之间的连接需要是封闭而加密的。
  4. 整个过程应该是可靠稳定的,我还用过其他的路由器/组合设备,但是效果不太理想

设备

我配置了一套满足上面条件并且在未来可以扩充的设备,它包含下面这些部件的使用:

  1. 9.7 英寸的 iPad Pro,这是本文写作时最强大、轻薄的 iOS 设备,苹果笔不是必需的,但是作为零件之一,当我在路上可以做一些编辑工作,所有的重活由树莓派做 ,其他设备只能通过 SSH 连接就行。
  2. 安装了 Raspbian 操作系统树莓派 3(LCTT 译注:Raspbian 是基于 Debian 的树莓派操作系统)。
  3. 树莓派的 Mini SD卡盒子/外壳
  4. 128G 的优盘,对于我是够用了,你可以买个更大的。你也可以买个像这样的移动硬盘,但是树莓派没法通过 USB 给移动硬盘提供足够的电量,这意味你需要额外准备一个供电的 USB hub 以及电缆,这就破坏了我们让设备轻薄的初衷。
  5. SD 读卡器
  6. 另外的 SD 卡,我会使用几个 SD 卡,在用满之前就会立即换一个,这样就会让我在一次旅途当中的照片散布在不同的 SD 卡上。

下图展示了这些设备之间如何相互连接。

旅行时照片的备份-流程图

树莓派会作为一个安全的热点。它会创建一个自己的 WPA2 加密的 WIFI 网络,iPad Pro 会连入其中。虽然有很多在线教程教你如何创建 Ad Hoc 网络(计算机到计算机的单对单网络),还更简单一些,但是它的连接是不加密的,而且附件的设备很容易就能连接进去。因此我选择创建 WIFI 网络。

相机的 SD 卡通过 SD 读卡器插到树莓派 USB 端口之一,128G 的大容量优盘一直插在树莓派的另外一个 USB 端口上,我选择了一款闪迪的,因为体积比较小。主要的思路就是通过 Python 脚本把 SD 卡的照片备份到优盘上,备份过程是增量备份,每次脚本运行时都只有变化的(比如新拍摄的照片)部分会添加到备份文件夹中,所以这个过程特别快。如果你有很多的照片或者拍摄了很多 RAW 格式的照片,在就是个巨大的优势。iPad 将用来运行 Python 脚本,而且用来浏览 SD 卡和优盘的文件。

作为额外的好处,如果给树莓派连上一根能上网的网线(比如通过以太网口),那么它就可以共享互联网连接给那些通过 WIFI 连入的设备。

1. 树莓派的设置

这部分需要你卷起袖子亲自动手了,我们要用到 Raspbian 的命令行模式,我会尽可能详细的介绍,方便大家进行下去。

安装和配置 Raspbian

给树莓派连接鼠标、键盘和 LCD 显示器,将 SD 卡插到树莓派上,按照树莓派官网的步骤安装 Raspbian。

安装完后,打开 Raspbian 的终端,执行下面的命令:

sudo apt-get update
sudo apt-get upgrade

这将升级机器上所有的软件到最新,我将树莓派连接到本地网络,而且为了安全更改了默认的密码。

Raspbian 默认开启了 SSH,这样所有的设置可以在一个远程的设备上完成。我也设置了 RSA 验证,但这是可选的功能,可以在这里查看更多信息。

这是一个在 Mac 上在 iTerm 里建立 SSH 连接到树莓派上的截图14。(LCTT 译注:原文图丢失。)

建立 WPA2 加密的 WIFI AP

安装过程基于这篇文章,根据我的情况进行了调整。

1. 安装软件包

我们需要安装下面的软件包:

sudo apt-get install hostapd
sudo apt-get install dnsmasq

hostapd 用来使用内置的 WiFi 来创建 AP,dnsmasp 是一个组合的 DHCP 和 DNS 服务其,很容易设置。

2. 编辑 dhcpcd.conf

通过以太网连接树莓派,树莓派上的网络接口配置由 dhcpd 控制,因此我们首先忽略这一点,将 wlan0 设置为一个静态的 IP。

sudo nano /etc/dhcpcd.conf 命令打开 dhcpcd 的配置文件,在最后一行添加上如下内容:

denyinterfaces wlan0

注意:它必须放在如果已经有的其它接口行之上

3. 编辑接口

现在设置静态 IP,使用 sudo nano /etc/network/interfaces 打开接口配置文件,按照如下信息编辑wlan0部分:

allow-hotplug wlan0
iface wlan0 inet static
    address 192.168.1.1
    netmask 255.255.255.0
    network 192.168.1.0
    broadcast 192.168.1.255
#    wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

同样,然后 wlan1 编辑如下:

#allow-hotplug wlan1
#iface wlan1 inet manual
#    wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

重要: 使用 sudo service dhcpcd restart 命令重启 dhcpd服务,然后用 sudo ifdown eth0; sudo ifup wlan0 命令来重载wlan0的配置。

4. 配置 Hostapd

接下来,我们需要配置 hostapd,使用 sudo nano /etc/hostapd/hostapd.conf 命令创建一个新的配置文件,内容如下:

interface=wlan0

# Use the nl80211 driver with the brcmfmac driver
driver=nl80211

# This is the name of the network
ssid=YOUR_NETWORK_NAME_HERE

# Use the 2.4GHz band
hw_mode=g

# Use channel 6
channel=6

# Enable 802.11n
ieee80211n=1

# Enable QoS Support
wmm_enabled=1

# Enable 40MHz channels with 20ns guard interval
ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40]

# Accept all MAC addresses
macaddr_acl=0

# Use WPA authentication
auth_algs=1

# Require clients to know the network name
ignore_broadcast_ssid=0

# Use WPA2
wpa=2

# Use a pre-shared key
wpa_key_mgmt=WPA-PSK

# The network passphrase
wpa_passphrase=YOUR_NEW_WIFI_PASSWORD_HERE

# Use AES, instead of TKIP
rsn_pairwise=CCMP

配置完成后,我们需要告诉dhcpcd 在系统启动运行时到哪里寻找配置文件。 使用 sudo nano /etc/default/hostapd 命令打开默认配置文件,然后找到#DAEMON_CONF="" 替换成DAEMON_CONF="/etc/hostapd/hostapd.conf"

5. 配置 Dnsmasq

自带的 dnsmasp 配置文件包含很多信息方便你使用它,但是我们不需要那么多选项,我建议把它移动到别的地方(而不要删除它),然后自己创建一个新文件:

sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.orig  
sudo nano /etc/dnsmasq.conf  

粘贴下面的信息到新文件中:

interface=wlan0      # Use interface wlan0
listen-address=192.168.1.1 # Explicitly specify the address to listen on
bind-interfaces      # Bind to the interface to make sure we aren't sending things elsewhere
server=8.8.8.8       # Forward DNS requests to Google DNS
domain-needed        # Don't forward short names
bogus-priv           # Never forward addresses in the non-routed address spaces.
dhcp-range=192.168.1.50,192.168.1.100,12h # Assign IP addresses in that range  with a 12 hour lease time

6. 设置 IPv4 转发

最后我们需要做的事就是配置包转发,用 sudo nano /etc/sysctl.conf 命令打开 sysctl.conf 文件,将包含 net.ipv4.ip_forward=1的那一行之前的#号删除,它将在下次重启时生效。

我们还需要给连接到树莓派的设备通过 WIFI 分享互联网连接,做一个 wlan0eth0 之间的 NAT。我们可以参照下面的脚本来实现。

sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE  
sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT  
sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT  

我命名这个脚本名为 hotspot-boot.sh,然后让它可以执行:

sudo chmod 755 hotspot-boot.sh

该脚本应该在树莓派启动的时候运行。有很多方法实现,下面是我实现的方式:

  1. 把文件放到/home/pi/scripts目录下。
  2. 输入sudo nano /etc/rc.local命令编辑 rc.local 文件,将运行该脚本的命令放到 exit 0之前。(更多信息参照这里)。

编辑后rc.local看起来像这样:

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

sudo /home/pi/scripts/hotspot-boot.sh &

exit 0

安装 Samba 服务和 NTFS 兼容驱动

我们要安装下面几个软件来启用 samba 协议,使文件浏览器能够访问树莓派分享的文件夹,ntfs-3g 可以使我们能够访问移动硬盘中 ntfs 文件系统的文件。

sudo apt-get install ntfs-3g
sudo apt-get install samba samba-common-bin

你可以参照这些文档来配置 Samba。

重要提示:参考的文档介绍的是挂载外置硬盘到树莓派上,我们不这样做,是因为在这篇文章写作的时候,树莓派在启动时的 auto-mounts 功能同时将 SD 卡和优盘挂载到/media/pi/上,该文章有一些多余的功能我们也不会采用。

2. Python 脚本

树莓派配置好后,我们需要开发脚本来实际拷贝和备份照片。注意,这个脚本只是提供了特定的自动化备份进程,如果你有基本的 Linux/树莓派命令行操作的技能,你可以 ssh 进树莓派,然后创建需要的文件夹,使用cprsync命令拷贝你自己的照片从一个设备到另外一个设备上。在脚本里我们用rsync命令,这个命令比较可靠而且支持增量备份。

这个过程依赖两个文件,脚本文件自身和backup_photos.conf这个配置文件,后者只有几行包含被挂载的目的驱动器(优盘)和应该挂载到哪个目录,它看起来是这样的:

mount folder=/media/pi/
destination folder=PDRIVE128GB

重要提示:在这个符号=前后不要添加多余的空格,否则脚本会失效。

下面是这个 Python 脚本,我把它命名为backup_photos.py,把它放到了/home/pi/scripts/目录下,我在每行都做了注释可以方便的查看各行的功能。

#!/usr/bin/python3

import os
import sys
from sh import rsync

'''
脚本将挂载到 /media/pi 的 SD 卡上的内容复制到目的磁盘的同名目录下,目的磁盘的名字在 .conf文件里定义好了。


Argument:  label/name of the mounted SD Card.
'''

CONFIG_FILE = '/home/pi/scripts/backup_photos.conf'
ORIGIN_DEV = sys.argv[1]

def create_folder(path):

    print ('attempting to create destination folder: ',path)
    if not os.path.exists(path):
        try: 
            os.mkdir(path)
            print ('Folder created.')
        except:
            print ('Folder could not be created. Stopping.')
            return
    else:
        print ('Folder already in path. Using that instead.')



confFile = open(CONFIG_FILE,'rU') 
#重要:: rU 选项将以统一换行模式打开文件,
#所以 \n 和/或 \r 都被识别为一个新行。

confList = confFile.readlines()
confFile.close()


for line in confList:
    line = line.strip('\n')

    try:
        name , value = line.split('=')

        if name == 'mount folder':
            mountFolder = value
        elif name == 'destination folder':
            destDevice = value


    except ValueError:
        print ('Incorrect line format. Passing.')
        pass


destFolder = mountFolder+destDevice+'/'+ORIGIN_DEV
create_folder(destFolder)

print ('Copying files...')

# 取消这行备注将删除不在源处的文件
# rsync("-av", "--delete", mountFolder+ORIGIN_DEV, destFolder)
rsync("-av", mountFolder+ORIGIN_DEV+'/', destFolder)

print ('Done.')

3. iPad Pro 的配置

因为重活都由树莓派干了,文件不通过 iPad Pro 传输,这比我之前尝试的一种方案有巨大的优势。我们在 iPad 上只需要安装上 Prompt2 来通过 SSH 连接树莓派就行了,这样你既可以运行 Python 脚本也可以手动复制文件了。

iPad 用 Prompt2 通过 SSH 连接树莓派

因为我们安装了 Samba,我们可以以更图形化的方式访问连接到树莓派的 USB 设备,你可以看视频,在不同的设备之间复制和移动文件,文件浏览器对于这种用途非常完美。(LCTT 译注:原文视频丢失。)

4. 将它们结合在一起

我们假设SD32GB-03是连接到树莓派 USB 端口之一的 SD 卡的卷标,PDRIVE128GB是那个优盘的卷标,也连接到设备上,并在上面指出的配置文件中定义好。如果我们想要备份 SD 卡上的图片,我们需要这么做:

  1. 给树莓派加电打开,将驱动器自动挂载好。
  2. 连接树莓派配置好的 WIFI 网络。
  3. Prompt2 这个 app 通过 SSH 连接到树莓派。
  4. 连接好后输入下面的命令:python3 backup_photos.py SD32GB-03

首次备份需要一些时间,这依赖于你的 SD 卡使用了多少容量。这意味着你需要一直保持树莓派和 iPad 设备连接不断,你可以在脚本运行之前通过 nohup 命令解决:

nohup python3 backup_photos.py SD32GB-03 &

运行完成的脚本如图所示

未来的定制

我在树莓派上安装了 vnc 服务,这样我可以通过其它计算机或在 iPad 上用 Remoter App连接树莓派的图形界面,我安装了 BitTorrent Sync 用来远端备份我的图片,当然需要先设置好。当我有了可以运行的解决方案之后,我会补充我的文章。

你可以在下面发表你的评论和问题,我会在此页下面回复。


via: http://www.movingelectrons.net/blog/2016/06/26/backup-photos-while-traveling-with-a-raspberry-pi.html

作者:Lenin 译者:jiajia9linuxer 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

你在 Python 中用过异步编程吗?本文中我会告诉你怎样做,而且用一个能工作的例子来展示它:这是一个流行的贪吃蛇游戏,而且是为多人游戏而设计的。

介绍和理论部分参见“第一部分 异步化”。

3、编写游戏循环主体

游戏循环是每一个游戏的核心。它持续地运行以读取玩家的输入、更新游戏的状态,并且在屏幕上渲染游戏结果。在在线游戏中,游戏循环分为客户端和服务端两部分,所以一般有两个循环通过网络通信。通常客户端的角色是获取玩家输入,比如按键或者鼠标移动,将数据传输给服务端,然后接收需要渲染的数据。服务端处理来自玩家的所有数据,更新游戏的状态,执行渲染下一帧的必要计算,然后将结果传回客户端,例如游戏中对象的新位置。如果没有可靠的理由,不混淆客户端和服务端的角色是一件很重要的事。如果你在客户端执行游戏逻辑的计算,很容易就会和其它客户端失去同步,其实你的游戏也可以通过简单地传递客户端的数据来创建。

游戏循环的一次迭代称为一个 嘀嗒 tick 。嘀嗒是一个事件,表示当前游戏循环的迭代已经结束,下一帧(或者多帧)的数据已经就绪。

在后面的例子中,我们使用相同的客户端,它使用 WebSocket 从一个网页上连接到服务端。它执行一个简单的循环,将按键码发送给服务端,并显示来自服务端的所有信息。客户端代码戳这里

例子 3.1:基本游戏循环

我们使用 aiohttp 库来创建游戏服务器。它可以通过 asyncio 创建网页服务器和客户端。这个库的一个优势是它同时支持普通 http 请求和 websocket。所以我们不用其他网页服务器来渲染游戏的 html 页面。

下面是启动服务器的方法:

app = web.Application()
app["sockets"] = []

asyncio.ensure_future(game_loop(app))

app.router.add_route('GET', '/connect', wshandler)
app.router.add_route('GET', '/', handle)

web.run_app(app)

web.run_app 是创建服务主任务的快捷方法,通过它的 run_forever() 方法来执行 asyncio 事件循环。建议你查看这个方法的源码,弄清楚服务器到底是如何创建和结束的。

app 变量就是一个类似于字典的对象,它用于在所连接的客户端之间共享数据。我们使用它来存储连接的套接字的列表。随后会用这个列表来给所有连接的客户端发送消息。asyncio.ensure_future() 调用会启动主游戏循环的任务,每隔2 秒向客户端发送嘀嗒消息。这个任务会在同样的 asyncio 事件循环中和网页服务器并行执行。

有两个网页请求处理器:handle 是提供 html 页面的处理器;wshandler 是主要的 websocket 服务器任务,处理和客户端之间的交互。在事件循环中,每一个连接的客户端都会创建一个新的 wshandler 任务。这个任务会添加客户端的套接字到列表中,以便 game_loop 任务可以给所有的客户端发送消息。然后它将随同消息回显客户端的每个击键。

在启动的任务中,我们在 asyncio 的主事件循环中启动 worker 循环。任务之间的切换发生在它们之间任何一个使用 await语句来等待某个协程结束时。例如 asyncio.sleep 仅仅是将程序执行权交给调度器一段指定的时间;ws.receive 等待 websocket 的消息,此时调度器可能切换到其它任务。

在浏览器中打开主页,连接上服务器后,试试随便按下键。它们的键值会从服务端返回,每隔 2 秒这个数字会被游戏循环中发给所有客户端的嘀嗒消息所覆盖。

我们刚刚创建了一个处理客户端按键的服务器,主游戏循环在后台做一些处理,周期性地同时更新所有的客户端。

例子 3.2: 根据请求启动游戏

在前一个例子中,在服务器的生命周期内,游戏循环一直运行着。但是现实中,如果没有一个人连接服务器,空运行游戏循环通常是不合理的。而且,同一个服务器上可能有不同的“游戏房间”。在这种假设下,每一个玩家“创建”一个游戏会话(比如说,多人游戏中的一个比赛或者大型多人游戏中的副本),这样其他用户可以加入其中。当游戏会话开始时,游戏循环才开始执行。

在这个例子中,我们使用一个全局标记来检测游戏循环是否在执行。当第一个用户发起连接时,启动它。最开始,游戏循环没有执行,标记设置为 False。游戏循环是通过客户端的处理方法启动的。

  if app["game_is_running"] == False:
        asyncio.ensure_future(game_loop(app))

game_loop() 运行时,这个标记设置为 True;当所有客户端都断开连接时,其又被设置为 False

例子 3.3:管理任务

这个例子用来解释如何和任务对象协同工作。我们把游戏循环的任务直接存储在游戏循环的全局字典中,代替标记的使用。在像这样的一个简单例子中并不一定是最优的,但是有时候你可能需要控制所有已经启动的任务。

    if app["game_loop"] is None or \
       app["game_loop"].cancelled():
        app["game_loop"] = asyncio.ensure_future(game_loop(app))

这里 ensure_future() 返回我们存放在全局字典中的任务对象,当所有用户都断开连接时,我们使用下面方式取消任务:

    app["game_loop"].cancel()

这个 cancel() 调用将通知调度器不要向这个协程传递执行权,而且将它的状态设置为已取消:cancelled,之后可以通过 cancelled() 方法来检查是否已取消。这里有一个值得一提的小注意点:当你持有一个任务对象的外部引用时,而这个任务执行中发生了异常,这个异常不会抛出。取而代之的是为这个任务设置一个异常状态,可以通过 exception() 方法来检查是否出现了异常。这种悄无声息地失败在调试时不是很有用。所以,你可能想用抛出所有异常来取代这种做法。你可以对所有未完成的任务显式地调用 result() 来实现。可以通过如下的回调来实现:

    app["game_loop"].add_done_callback(lambda t: t.result())

如果我们打算在我们代码中取消这个任务,但是又不想产生 CancelError 异常,有一个检查 cancelled 状态的点:

    app["game_loop"].add_done_callback(lambda t: t.result()
                                       if not t.cancelled() else None)

注意仅当你持有任务对象的引用时才需要这么做。在前一个例子,所有的异常都是没有额外的回调,直接抛出所有异常。

例子 3.4:等待多个事件

在许多场景下,在客户端的处理方法中你需要等待多个事件的发生。除了来自客户端的消息,你可能需要等待不同类型事件的发生。比如,如果你的游戏时间有限制,那么你可能需要等一个来自定时器的信号。或者你需要使用管道来等待来自其它进程的消息。亦或者是使用分布式消息系统的网络中其它服务器的信息。

为了简单起见,这个例子是基于例子 3.1。但是这个例子中我们使用 Condition 对象来与已连接的客户端保持游戏循环的同步。我们不保存套接字的全局列表,因为只在该处理方法中使用套接字。当游戏循环停止迭代时,我们使用 Condition.notify_all() 方法来通知所有的客户端。这个方法允许在 asyncio 的事件循环中使用发布/订阅的模式。

为了等待这两个事件,首先我们使用 ensure_future() 来封装任务中这个可等待对象。

    if not recv_task:
        recv_task = asyncio.ensure_future(ws.receive())
    if not tick_task:
        await tick.acquire()
        tick_task = asyncio.ensure_future(tick.wait())

在我们调用 Condition.wait() 之前,我们需要在它后面获取一把锁。这就是我们为什么先调用 tick.acquire() 的原因。在调用 tick.wait() 之后,锁会被释放,这样其他的协程也可以使用它。但是当我们收到通知时,会重新获取锁,所以在收到通知后需要调用 tick.release() 来释放它。

我们使用 asyncio.wait() 协程来等待两个任务。

    done, pending = await asyncio.wait(
        [recv_task,
         tick_task],
        return_when=asyncio.FIRST_COMPLETED)

程序会阻塞,直到列表中的任意一个任务完成。然后它返回两个列表:执行完成的任务列表和仍然在执行的任务列表。如果任务执行完成了,其对应变量赋值为 None,所以在下一个迭代时,它可能会被再次创建。

例子 3.5: 结合多个线程

在这个例子中,我们结合 asyncio 循环和线程,在一个单独的线程中执行主游戏循环。我之前提到过,由于 GIL 的存在,Python 代码的真正并行执行是不可能的。所以使用其它线程来执行复杂计算并不是一个好主意。然而,在使用 asyncio 时结合线程有原因的:当我们使用的其它库不支持 asyncio 时就需要。在主线程中调用这些库会阻塞循环的执行,所以异步使用他们的唯一方法是在不同的线程中使用他们。

我们使用 asyncio 循环的run_in_executor() 方法和 ThreadPoolExecutor 来执行游戏循环。注意 game_loop() 已经不再是一个协程了。它是一个由其它线程执行的函数。然而我们需要和主线程交互,在游戏事件到来时通知客户端。asyncio 本身不是线程安全的,它提供了可以在其它线程中执行你的代码的方法。普通函数有 call_soon_threadsafe(),协程有 run_coroutine_threadsafe()。我们在 notify() 协程中增加了通知客户端游戏的嘀嗒的代码,然后通过另外一个线程执行主事件循环。

def game_loop(asyncio_loop):
    print("Game loop thread id {}".format(threading.get_ident()))
    async def notify():
        print("Notify thread id {}".format(threading.get_ident()))
        await tick.acquire()
        tick.notify_all()
        tick.release()

    while 1:
        task = asyncio.run_coroutine_threadsafe(notify(), asyncio_loop)
        # blocking the thread
        sleep(1)
        # make sure the task has finished
        task.result()

当你执行这个例子时,你会看到 “Notify thread id” 和 “Main thread id” 相等,因为 notify() 协程在主线程中执行。与此同时 sleep(1) 在另外一个线程中执行,因此它不会阻塞主事件循环。

例子 3.6:多进程和扩展

单线程的服务器可能运行得很好,但是它只能使用一个 CPU 核。为了将服务扩展到多核,我们需要执行多个进程,每个进程执行各自的事件循环。这样我们需要在进程间交互信息或者共享游戏的数据。而且在一个游戏中经常需要进行复杂的计算,例如路径查找之类。这些任务有时候在一个游戏嘀嗒中没法快速完成。在协程中不推荐进行费时的计算,因为它会阻塞事件的处理。在这种情况下,将这个复杂任务交给其它并行执行的进程可能更合理。

最简单的使用多个核的方法是启动多个使用单核的服务器,就像之前的例子中一样,每个服务器占用不同的端口。你可以使用 supervisord 或者其它进程控制的系统。这个时候你需要一个像 HAProxy 这样的负载均衡器,使得连接的客户端分布在多个进程间。已经有一些可以连接 asyncio 和一些流行的消息及存储系统的适配系统。例如:

你可以在 github 或者 pypi 上找到其它的软件包,大部分以 aio 开头。

使用网络服务在存储持久状态和交换某些信息时可能比较有效。但是如果你需要进行进程间通信的实时处理,它的性能可能不足。此时,使用标准的 unix 管道可能更合适。asyncio 支持管道,在aiohttp仓库有个 使用管道的服务器的非常底层的例子

在当前的例子中,我们使用 Python 的高层类库 multiprocessing 来在不同的核上启动复杂的计算,使用 multiprocessing.Queue 来进行进程间的消息交互。不幸的是,当前的 multiprocessing 实现与 asyncio 不兼容。所以每一个阻塞方法的调用都会阻塞事件循环。但是此时线程正好可以起到帮助作用,因为如果在不同线程里面执行 multiprocessing 的代码,它就不会阻塞主线程。所有我们需要做的就是把所有进程间的通信放到另外一个线程中去。这个例子会解释如何使用这个方法。和上面的多线程例子非常类似,但是我们从线程中创建的是一个新的进程。

def game_loop(asyncio_loop):
    # coroutine to run in main thread
    async def notify():
        await tick.acquire()
        tick.notify_all()
        tick.release()

    queue = Queue()

    # function to run in a different process
    def worker():
        while 1:
            print("doing heavy calculation in process {}".format(os.getpid()))
            sleep(1)
            queue.put("calculation result")

    Process(target=worker).start()

    while 1:
        # blocks this thread but not main thread with event loop
        result = queue.get()
        print("getting {} in process {}".format(result, os.getpid()))
        task = asyncio.run_coroutine_threadsafe(notify(), asyncio_loop)
        task.result()

这里我们在另外一个进程中运行 worker() 函数。它包括一个执行复杂计算并把计算结果放到 queue 中的循环,这个 queuemultiprocessing.Queue 的实例。然后我们就可以在另外一个线程的主事件循环中获取结果并通知客户端,就和例子 3.5 一样。这个例子已经非常简化了,它没有合理的结束进程。而且在真实的游戏中,我们可能需要另外一个队列来将数据传递给 worker

有一个项目叫 aioprocessing,它封装了 multiprocessing,使得它可以和 asyncio 兼容。但是实际上它只是和上面例子使用了完全一样的方法:从线程中创建进程。它并没有给你带来任何方便,除了它使用了简单的接口隐藏了后面的这些技巧。希望在 Python 的下一个版本中,我们能有一个基于协程且支持 asynciomultiprocessing 库。

注意!如果你从主线程或者主进程中创建了一个不同的线程或者子进程来运行另外一个 asyncio 事件循环,你需要显式地使用 asyncio.new_event_loop() 来创建循环,不然的话可能程序不会正常工作。

via: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-and-asyncio-writing-game-loop/

作者:Kyrylo Subbotin 译者:chunyang-wen 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

(题图来自:deviantart.com

四月份,我们宣布了对 HTTP/2 服务端推送技术的支持,我们是通过 HTTP 的 Link 头部来实现这项支持的。我的同事 John 曾经通过一个例子演示了在 PHP 里支持服务端推送功能是多么的简单。

我们想让现今使用 Node.js 构建的网站能够更加轻松的获得性能提升。为此,我们开发了 netjet 中间件,它可以解析应用生成的 HTML 并自动添加 Link 头部。当在一个示例的 Express 应用中使用这个中间件时,我们可以看到应用程序的输出多了如下 HTTP 头:

本博客是使用 Ghost(LCTT 译注:一个博客发布平台)进行发布的,因此如果你的浏览器支持 HTTP/2,你已经在不知不觉中享受了服务端推送技术带来的好处了。接下来,我们将进行更详细的说明。

netjet 使用了带有定制插件的 PostHTML 来解析 HTML。目前,netjet 用它来查找图片、脚本和外部 CSS 样式表。你也可以用其它的技术来实现这个。

在响应过程中增加 HTML 解析器有个明显的缺点:这将增加页面加载的延时(到加载第一个字节所花的时间)。大多数情况下,所新增的延时被应用里的其他耗时掩盖掉了,比如数据库访问。为了解决这个问题,netjet 包含了一个可调节的 LRU 缓存,该缓存以 HTTP 的 ETag 头部作为索引,这使得 netjet 可以非常快的为已经解析过的页面插入 Link 头部。

不过,如果我们现在从头设计一款全新的应用,我们就应该考虑把页面内容和页面中的元数据分开存放,从而整体地减少 HTML 解析和其它可能增加的延时了。

任意的 Node.js HTML 框架,只要它支持类似 Express 这样的中间件,netjet 都是能够兼容的。只要把 netjet 像下面这样加到中间件加载链里就可以了。

var express = require('express');
var netjet = require('netjet');
var root = '/path/to/static/folder';

express()
  .use(netjet({
    cache: {
      max: 100
    }
  }))
  .use(express.static(root))
  .listen(1337);

稍微加点代码,netjet 也可以摆脱 HTML 框架,独立工作:

var http = require('http');
var netjet = require('netjet');

var port = 1337;
var hostname = 'localhost';
var preload = netjet({
  cache: {
    max: 100
  }
});

var server = http.createServer(function (req, res) {
  preload(req, res, function () {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/html');
      res.end('<!doctype html><h1>Hello World</h1>');
  });
});

server.listen(port, hostname, function () {
  console.log('Server running at http://' + hostname + ':' + port+ '/');
});

netjet 文档里有更多选项的信息。

查看推送了什么数据

访问本文时,通过 Chrome 的开发者工具,我们可以轻松的验证网站是否正在使用服务器推送技术(LCTT 译注: Chrome 版本至少为 53)。在“Network”选项卡中,我们可以看到有些资源的“Initiator”这一列中包含了Push字样,这些资源就是服务器端推送的。

不过,目前 Firefox 的开发者工具还不能直观的展示被推送的资源。不过我们可以通过页面响应头部里的cf-h2-pushed头部看到一个列表,这个列表包含了本页面主动推送给浏览器的资源。

希望大家能够踊跃为 netjet 添砖加瓦,我也乐于看到有人正在使用 netjet。

Ghost 和服务端推送技术

Ghost 真是包罗万象。在 Ghost 团队的帮助下,我把 netjet 也集成到里面了,而且作为测试版内容可以在 Ghost 的 0.8.0 版本中用上它。

如果你正在使用 Ghost,你可以通过修改 config.js、并在production配置块中增加 preloadHeaders 选项来启用服务端推送。

production: {
  url: 'https://my-ghost-blog.com',
  preloadHeaders: 100,
  // ...
}

Ghost 已经为其用户整理了一篇支持文档

总结

使用 netjet,你的 Node.js 应用也可以使用浏览器预加载技术。并且 CloudFlare 已经使用它在提供了 HTTP/2 服务端推送了。


via: https://blog.cloudflare.com/accelerating-node-js-applications-with-http-2-server-push/

作者:Terin Stock 译者:echoma 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

你在 Python 中用过异步编程吗?本文中我会告诉你怎样做,而且用一个能工作的例子来展示它:这是一个流行的贪吃蛇游戏,而且是为多人游戏而设计的。

1、简介

在技术和文化领域,大规模多人在线游戏(MMO)毋庸置疑是我们当今世界的潮流之一。很长时间以来,为一个 MMO 游戏写一个服务器这件事总是会涉及到大量的预算与复杂的底层编程技术,不过在最近这几年,事情迅速发生了变化。基于动态语言的现代框架允许在中档的硬件上面处理大量并发的用户连接。同时,HTML5 和 WebSockets 标准使得创建基于实时图形的游戏的直接运行至浏览器上的客户端成为可能,而不需要任何的扩展。

对于创建可扩展的非堵塞性的服务器来说,Python 可能不是最受欢迎的工具,尤其是和在这个领域里最受欢迎的 Node.js 相比而言。但是最近版本的 Python 正在改变这种现状。asyncio 的引入和一个特别的 async/await 语法使得异步代码看起来像常规的阻塞代码一样,这使得 Python 成为了一个值得信赖的异步编程语言,所以我将尝试利用这些新特点来创建一个多人在线游戏。

2、异步

一个游戏服务器应该可以接受尽可能多的用户并发连接,并实时处理这些连接。一个典型的解决方案是创建线程,然而在这种情况下并不能解决这个问题。运行上千的线程需要 CPU 在它们之间不停的切换(这叫做上下文切换),这将导致开销非常大,效率很低下。更糟糕的是使用进程来实现,因为,不但如此,它们还会占用大量的内存。在 Python 中,甚至还有一个问题,Python 的解释器(CPython)并不是针对多线程设计的,相反它主要针对于单线程应用实现最大的性能。这就是为什么它使用 GIL(global interpreter lock),这是一个不允许同时运行多线程 Python 代码的架构,以防止同一个共享对象出现使用不可控。正常情况下,在当前线程正在等待的时候,解释器会转换到另一个线程,通常是等待一个 I/O 的响应(举例说,比如等待 Web 服务器的响应)。这就允许在你的应用中实现非阻塞 I/O 操作,因为每一个操作仅仅阻塞一个线程而不是阻塞整个服务器。然而,这也使得通常的多线程方案变得几近无用,因为它不允许你并发执行 Python 代码,即使是在多核心的 CPU 上也是这样。而与此同时,在一个单一线程中拥有非阻塞 I/O 是完全有可能的,因而消除了经常切换上下文的需要。

实际上,你可以用纯 Python 代码来实现一个单线程的非阻塞 I/O。你所需要的只是标准的 select 模块,这个模块可以让你写一个事件循环来等待未阻塞的 socket 的 I/O。然而,这个方法需要你在一个地方定义所有 app 的逻辑,用不了多久,你的 app 就会变成非常复杂的状态机。有一些框架可以简化这个任务,比较流行的是 tornadetwisted。它们被用来使用回调方法实现复杂的协议(这和 Node.js 比较相似)。这种框架运行在它自己的事件循环中,按照定义的事件调用你的回调函数。并且,这或许是一些情况的解决方案,但是它仍然需要使用回调的方式编程,这使你的代码变得碎片化。与写同步代码并且并发地执行多个副本相比,这就像我们在普通的线程上做的一样。在单个线程上这为什么是不可能的呢?

这就是为什么出现微线程(microthread)概念的原因。这个想法是为了在一个线程上并发执行任务。当你在一个任务中调用阻塞的方法时,有一个叫做“manager” (或者“scheduler”)的东西在执行事件循环。当有一些事件准备处理的时候,一个 manager 会转移执行权给一个任务,并等着它执行完毕。任务将一直执行,直到它遇到一个阻塞调用,然后它就会将执行权返还给 manager。

微线程也称为轻量级线程(lightweight threads)或绿色线程(green threads)(来自于 Java 中的一个术语)。在伪线程中并发执行的任务叫做 tasklets、greenlets 或者协程(coroutines)。

Python 中的微线程最早的实现之一是 Stackless Python。它之所以这么知名是因为它被用在了一个叫 EVE online 的非常有名的在线游戏中。这个 MMO 游戏自称说在一个持久的“宇宙”中,有上千个玩家在做不同的活动,这些都是实时发生的。Stackless 是一个独立的 Python 解释器,它代替了标准的函数栈调用,并且直接控制程序运行流程来减少上下文切换的开销。尽管这非常有效,这个解决方案不如在标准解释器中使用“软”库更流行,像 eventletgevent 的软件包配备了修补过的标准 I/O 库,I/O 函数会将执行权传递到内部事件循环。这使得将正常的阻塞代码转变成非阻塞的代码变得简单。这种方法的一个缺点是从代码上看这并不分明,它的调用是非阻塞的。新版本的 Python 引入了本地协程作为生成器的高级形式。在 Python 的 3.4 版本之后,引入了 asyncio 库,这个库依赖于本地协程来提供单线程并发。但是仅仅到了 Python 3.5 ,协程就变成了 Python 语言的一部分,使用新的关键字 async 和 await 来描述。这是一个简单的例子,演示了使用 asyncio 来运行并发任务。

import asyncio

async def my_task(seconds):
    print("start sleeping for {} seconds".format(seconds))
    await asyncio.sleep(seconds)
    print("end sleeping for {} seconds".format(seconds))

all_tasks = asyncio.gather(my_task(1), my_task(2))
loop = asyncio.get_event_loop()
loop.run_until_complete(all_tasks)
loop.close()    

我们启动了两个任务,一个睡眠 1 秒钟,另一个睡眠 2 秒钟,输出如下:

start sleeping for 1 seconds
start sleeping for 2 seconds
end sleeping for 1 seconds
end sleeping for 2 seconds

正如你所看到的,协程不会阻塞彼此——第二个任务在第一个结束之前启动。这发生的原因是 asyncio.sleep 是协程,它会返回执行权给调度器,直到时间到了。

在下一节中,我们将会使用基于协程的任务来创建一个游戏循环。


via: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-asyncio-getting-asynchronous/

作者:Kyrylo Subbotin 译者:xinglianfly 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

(题图来自:deviantart.net