分类 技术 下的文章

需要远程工作的看一下。使用这个有用的开源解决方案,从任何地方快速地连接和访问你的所有设备。

随着对连接和远程工作的需求的增长,访问远程计算资源变得越来越重要。但是,提供对设备和硬件的外部访问的要求使此任务变得复杂而有风险。旨在减少此类麻烦的 ShellHub 是一个云服务器,允许从任何外部网络常规访问这些设备。

ShellHub 是一个 Apache 2.0 许可的开源解决方案,它可满足所有这些需求,并允许用户通过一个帐户连接和管理多个设备。它的开发是为了方便开发者和程序员的工作,使得任何硬件架构的 Linux 设备的远程访问成为可能。

仔细观察,ShellHub 方案使用 HTTP 传输层来封装 SSH 协议。这种传输层的选择可以在大多数网络上无缝使用,因为大多数公司的防火墙规则和策略通常都可以使用并接受它。

下面这些示例使用 2020 年 6 月 10 日发布的 ShellHub 版本 0.3.2。

使用 ShellHub

要访问该平台,只需进入 shellhub.io 并注册一个自己的帐户。你的注册数据将帮助开发团队了解用户资料并提供有关如何改进平台的更多信息。

图 1:shellhub.io 中的注册表格

ShellHub 有直观、简洁的界面,这让所有的信息和功能都能以最快的方式呈现。注册后,你会看到一块仪表板,这时可以注册你的第一台设备。

添加设备

要启用通过 ShellHub 连接设备,你需要生成一个标识符,它用于在设备连接到服务器时对你的设备进行身份验证。

此标识必须配置在代理(ShellHub 客户端)内部,该代理必须与镜像一起保存在设备中,或者必须添加为 Docker 容器。

ShellHub 默认使用 Docker 运行代理,这非常方便,因为它在现有系统上提供了一种无痛的添加方式,支持 Docker 是唯一的要求。要添加设备,你需要粘贴命令行,它显示在 ShellHub Cloud 的对话框中(请参见图 2)。

图 2:将设备添加到 ShellHub Cloud

设备默认使用它的 MAC 地址作为其主机名。在内部,该设备由其密钥标识,这是在设备注册期间生成的,用于与服务器进行身份验证。

访问设备

要访问你的设备,只需进入仪表板中的“查看所有设备”,或单击左侧菜单上的“设备”。这将列出你所有已注册的设备。

设备状态可在页面上轻松看到。在线设备旁边会显示一个绿色图标,可以单击终端图标进行连接。你接着输入密码,最后单击”连接“按钮,请参见(图 3)。

图 3:使用网络上的终端访问设备

另一种访问设备的方法是从类似 PuTTYTermius 之类的 SSH 客户端,甚至 Linux 终端访问。我们可以使用称为 SSHID 的 ShellHub 标识作为连接的目的地址(例如 ssh username@SSHID)。图 4 说明了如何使用在终端中使用 Linux SSH 客户端连接到我们的计算机。

图 4:使用 Linux 终端连接到设备

无论你何时登录 ShellHub Cloud 平台,你都可以访问仪表板上的所有已注册设备,这样你可以随时随地访问它们。ShellHub 通过一个开源平台,以透明的方式为您与远程机器保持通信安全的过程增加了简单性。

GitHub 上加入 ShellHub 社区,或随时通过 Gitter 或通过电子邮件 [email protected] 向开发团队发送你的建议或反馈。我们很乐意收到社区成员的贡献!


via: https://opensource.com/article/20/7/linux-shellhub

作者:Domarys 选题:lujun9972 译者:geekpi 校对:wxy

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

最近两年,树莓派 Zero树莓派 Zero W 作为新成员出现在树莓派产品线中。这些超小型的单板计算机大受欢迎,而且会在相当长的一段时间内成为创客和 DIY 社区的 树莓派项目 的一部分。

为了实现更小尺寸和有效地控制价位,它们不得不在许多特性上做出让步,如放弃专用网口、采用更慢的处理器等(与其它们同族的全功能版相比)。

早先的一篇文章中,我们罗列了 树莓派的最佳替代品。这里,我将给出树莓派 Zero 和树莓派 Zero W 的一些替代品。

树莓派 Zero 替代品:应用于物联网和嵌入式项目的超小单板计算机

受益于开源设计和开源软件栈,我们有为不同项目提供的具有各种功能集的优秀替代品。所有这些板子运行的都是嵌入式 Linux 的各种变体。

尽管树莓派 Zero 和树莓派 Zero W 的发布价格分别是 5 美元和 10 美元,但即使是在美国境内,也很难按这个价格买到。在美国之外,更是要花 12 和 20 美元才有可能买到。

知道了这些,让我们看看一些 20 美元以下的树莓派 Zero 替代品。

1、香蕉派 BPI M2 Zero

香蕉派 M2 Zero 售价 18 美元,与树莓派 Zero W 拥有相同的布局。它看起来像是树莓派 Zero W 的复制品,但也仅仅是外形像而已。它使用的是更快的全志 H2+ SOC 芯片,而且可以外接网络模块。它还可以运行多种基于 Linux 的操作系统。

主要参数

  • 全志 H2+ 四核 Cortex-A7 处理器,Mali400MP2 GPU,H265/HEVC 1080P
  • 512M DDR3 内存(与 GPU 共享)
  • 40 针接口,与树莓派 3 兼容
  • 板载 WiFi(AP6212)、蓝牙,外部天线连接口
  • CSI 摄像头输入接口
  • 电源及复位按钮
  • Mini HDMI 视频输出

详情可参阅 Banana Pi Wiki

2、香蕉派 BPI-M2 Magic(BPi-M2M)

这个型号有 2 个变体,无板载 eMMC 闪存的标价 20 美元。这是相对其尺寸而言拥有更大处理能力的小型单板计算机。我觉得毫无疑问它很适合作为物联网和自动家居中的触控面板和显示面板。其板载电源管理系统也很有吸引力。

主要参数

  • 全志 A33/R16 四核 ARM Cortex-A7 处理器,MALI 400 MP2 GPU
  • 板载 802.11 b/g/n 2.4GHz WiFi(AP6212),蓝牙 4.0,支持 BLE
  • 512MB DDR3(与 GPU 共享)
  • MIPI 串行显示接口(DSI),4 数据通道
  • CSI 摄像头输入接口,最大分辨率 1080p 时可达 30 帧/秒
  • 板载话筒和电池管理
  • 无 HDMI 输出

详情可参阅 Banana Pi Wiki

3、香蕉派 BPI-P2 Maker

它是带有板载网络模块及支持网口供电(POE)的最小的单板计算机之一,此板售价 13 美元(带有 POE 模块的版本售价 19 美元)。和香蕉派 M2 Zero 一样,使用的是全志 H2+ SOC 芯片,是块非常有趣的板子。它具有板载 8 GB eMMC 存储空间及摄像头接口,同时具有 POE 功能,可用于 DIY 监控摄像,也可以作为基本的机器学习处理器使用。

主要参数

  • 处理器:全志 H2+,四核,Cortex-A7 架构
  • 512MB DDR 3 同步动态随机存取内存
  • 板载 WiFi(AP6212)和蓝牙
  • 板载 8G eMMC 闪存
  • 100M 网络
  • Mini HDMI
  • CSI 摄像头接口
  • 具有 POE 模块,支持 IEEE 802.3af PoE 标准

详情可参阅 Banana Pi Wiki

4、桔子派 Zero LTS

当前支持板载网络及 POE 的最小且最便宜的单板计算机,售价仅为 11.49 美元(256 MB 内存版本售价 9.49 美元)。其采用非常常见的全志 H2+ 处理器,并通过 GPIO 和 13 针功能头提供可靠的扩展方式。

主要参数

  • 全志 H2+ 四核 Cortex-A7 架构处理器,视频支持 H.265/HEVC 1080P
  • Mali400MP2 GPU @600MHz
  • 256MB/512MB DDR3 SDRAM(与 GPU 共用)(256 MB 版为标准版)
  • 10/100M 自适应网络,RJ45 网口,POE 功能默认关闭
  • WiFi 使用 XR819 方案,支持 IEEE 802.11 b/g/n
  • 26 针 GPIO 连接头
  • 13 针连接头,包含 2 路 USB、红外线接口和声音端口(MIC、AV)

详情可参阅 官方网站

5、桔子派 i96

尺寸仅有 6×3 平方厘米,个头最小的型号之一,售价 8.8 美元。该板使用 RDA8810PL 处理器,可作为相当先进的功能手机,(据厂商称)适合摄像应用,最高可以 30 帧/秒的速率采集 1080p 分辨率的图像。在同等价位产品中,输入输出功能出色。

主要参数

  • RDA8810PL ARM Cortex-A5 32 位单核处理器
  • Vivante 出品 GC860 GPU
  • 集成 256MB LPDDR2 SDRAM
  • RDA5991 方案的 WiFi 和蓝牙
  • CSI 摄像头输入接口
  • 40 针 GPIO 头

详情可参阅 官方网站

6、桔子派 PC

该板售价 15 美元,包含了很多功能。是在同等价位板子中少有的集成了 1 GB 内存的型号之一。采用全志 H3 SoC 芯片,可解码 4K HEVC/H.265 视频。具有 HDMI 输出口并支持 HDCP 和 CEC。该单板计算机可以通过安装软件成为一个很好用的媒体盒子。它竟然还配备了板载红外接收器和话筒。

主要参数

  • 全志 H3 四核 Cortex-A7 架构处理器,主频 1.6 GHz
  • 1GB DDR3 内存(与 GPU 共享)
  • 支持 4K 输出的 HDMI
  • CSI 摄像头接口,板载话筒
  • SD 卡槽
  • IR 接收器
  • 3.5mm 音频接口
  • 网络接口
  • 板上无 WiFi 和蓝牙

桔子派 PC 还有一种减配版,使用的是同样的处理器,只是内存容量要小。

详情可参阅 官方网站

7、桔子派 One 和桔子派 Lite

这两种板子与桔子派 PC 一样,使用的是全志 H3 处理器,但配备的是 512MB 内存。

主要参数

  • 全志 H3 四核处理器
  • HDMI 输出,支持 4K 高清
  • SD 卡槽
  • 512 MB DDR3 内存
  • CSI 摄像头接口

桔子派 Lite 售价 12 美元,不支持板载有线网络,但提供了 WiFi 联网功能。其具有板载话筒和红外接收器。详情可参阅 官方网站

桔子派 One 售价 11 美元,具有板载有线网络,但不支持 WiFi。详情可参阅 官方网站

在桔子派相关内容的最后,我想简单提一下他们提供的几种适合自定义应用的板子。

  • 桔子派 R1 – 具有双有线网口的小板子,可用于构建网络设备。
  • Orange Pi 2G IOT 和 Orange Pi 3G IOT - 这些板子分别提供了 2G 和 3G 蜂窝网络连接能力,适合作为物联网设备。

这些板子都不超过 20 美元,详情可参阅 官方网站

8、NanoPi Neo LTS

起步价 9.99 美元,该板非常袖珍(4x4 平方厘米),与桔子派 Zero 规格相似。不同的是,它使用的是更为强悍的全志 H3 SoC 芯片和高达 512MB 的内存。虽然无任何板载 WiFi 和蓝牙芯片,但可以通过 USB 接口外接相关器件获得相应功能。该板非常适合作为无显示器的 Linux 服务器、DNS 过滤器(如 Pi-Hole),同时也很适合作为物联网应用的边缘设备使用。通过 GPIO 可以扩展出你需要的很多功能。

主要参数

  • 全志 H3 四核处理器,Cortex A7 内核,主频 1.2 GHz
  • 512 MB 内存
  • Micro SD 卡槽(最高支持 128 GB)
  • 10/100 Mbps 网口
  • 冗余 GPIO 针可实现其它所需功能

可从 官方网站 了解详情及购买。

NanoPi NEO 的极简版称为 NanoPi NEO Core LTS,该极简版为满足工业应用需求增加了 eMMC 存储,且通过扩展 GPIO 提供了板载 USB 接口和板载网络接口。详情参阅 这里

Nano NEO Air 的 WiFi/蓝牙版称为 NanoPi NEO Air,提供了 eMMC 存储和摄像头输入接口,提供了板载 USB 接口和板载网络接口。详情参阅 这里.

9、Zero Pi

这是所有这里提到的板子中,我最感兴趣的板子之一,售价 9.99 美元,具有板载千兆网口,使用全志 H3 处理器,小而强悍,适合应用于网络相关场景。它可以运行 OpenWRT,和其配备的千兆网络相得益彰。在作为 DNS 服务器运行的同时,还可以运行 Pi-Hole 的多个实例。

主要参数

  • 全志 H3 四核处理器,Cortex A7 架构,主频最高 1.2GHz
  • 512MB 内存
  • USB 2.0 接口
  • 可运行 OpenWRT

可从 官方网站 了解详情及下单购买。

10、NanoPi NEO 2

NanoPi NEO 2 售价 19.99 美元,是 NanoPi NEO 的 2 倍。其外形尺寸与 NanoPi Neo 相同,但使用的是全志 H5 处理器,并配备了千兆网功能。这使得该板子个头小而性能强。

主要参数

  • 全志 H5 处理器,四核 64 位,高性能 Cortex A53 架构
  • Hexacore Mali450 GPU
  • 512MB 内存
  • 1Gbps 板载网络
  • 24 针 GPIO
  • 包含音频及红外之类功能的外接口

可从 官方网站 了解详情及下单购买。

上面的链接是关于 NanoPi 系列板子的。除此之外,还有一些其它有趣的板子,如配备双千兆网口的,以及针对摄像头应用的。

11、La Frite

出品商是 Le Potato,售价 20 美元,主要面向媒体消费及流媒体。它通过 HDMI 2.0 接口以 HDR 方式实现 1080p 视频播放,支持最新的安卓 9 /电视、上游的 Linux、u-boot、Kodi 等。

主要参数

  • Amlogic S805X 处理器,四核,Cortex-A53 架构,主频 1.2GHz
  • 最大 1GB DDR4 SDRAM
  • Amlogic 视频引擎 10,支持 H.264、H.265 和 VP9 解码,最高能以 60 帧/秒的速率实现 1080p 高清
  • 百兆网
  • 红外接器
  • 40 针 GPIO

详情可参阅 官方网站

12、Onion Omega2+

如果你需要的是应用于物联网场景的单板计算机,那么 Onion Omega 2+ 是替代树莓派 Zero 的很好选择。它是针对物联网的开发平台,运行的是基于 OpenWRT 的嵌入式 Linux 开发环境。

主要参数

  • MT7688 SoC 芯片
  • 2.4 GHz WiFi,支持 IEEE 802.11 b/g/n
  • 128 MB DDR2 内存
  • 32 MB 板载闪存
  • MicroSD 卡槽
  • USB 2.0
  • 12 个 GPIO 引针

你可以 13 美元 的单价从 官方网站 购买其极简版,并可以选配各种附件。

13、VoCore2

VoCore2 肯定是类似产品中最小的一款,极简版尺寸只有 1 英寸见方,售价 17.99 美元。小巧的尺寸使得其能方便的嵌入到不同的应用场合,而且允许根据需要扩充不同的特性。该板使用过的是联发科针对中低端路由器的处理器 MT7628 处理器。生产商声明将持续供应至 2025 年,这是个不错的消息。

主要参数

  • 联发科 MT7628 处理器,主频 580 MHz,MIPS 24K
  • 128MB DDR2 内存, 内存刷新率 166 MHz
  • 1 或 5 网口,100 Mbps 网络
  • 无线网,支持 802.11n,双收双发,网速最高 300Mbps
  • 板载 16M NOR 存储器,支持最大 2TB SDXC 卡
  • 一个板载 U.FL 槽(天线接口)

你可以从 此处 了解更多细节。

结束语

无可否认,不同规格和特性的各种单板计算机可以满足千变万化的使用需求。另外,绝大部分此类产品都基于开源设计,运行着开源软件。这绝对是硬核玩家不可多得的乐园。

由于新冠病毒的肆虐,要上手这些板子可能要稍费周折,期待情况能尽快好转!

如果各位知道树莓派 Zero 和树莓派 Zero W 的其它有趣的替代型号,请留言。我们根据情况检查充实。


via: https://itsfoss.com/raspberry-pi-zero-alternatives/

作者:Chinmay 选题:lujun9972 译者:silentdawn-zz 校对:wxy

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

ZeroMQ 是一个快速灵活的消息库,用于数据收集和不同编程语言间的数据共享。

作为软件工程师,我有多次在要求完成指定任务时感到浑身一冷的经历。其中有一次,我必须在一些新的硬件基础设施和云基础设施之间写一个接口,这些硬件需要 C 语言,而云基础设施主要是用 Python。

实现的方式之一是 用 C 写扩展模块,Python 支持 C 扩展的调用。快速浏览文档后发现,这需要编写大量的 C 代码。这样做的话,在有些情况下效果还不错,但不是我喜欢的方式。另一种方式就是将两个任务放在不同的进程中,并使用 ZeroMQ 消息库 在两者之间交换消息。

在发现 ZeroMQ 之前,遇到这种类型的情况时,我选择了编写扩展的方式。这种方式不算太差,但非常费时费力。如今,为了避免那些问题,我将一个系统细分为独立的进程,通过 通信套接字 发送消息来交换信息。这样,不同的编程语言可以共存,每个进程也变简单了,同时也容易调试。

ZeroMQ 提供了一个更简单的过程:

  1. 编写一小段 C 代码,从硬件读取数据,然后把发现的东西作为消息发送出去。
  2. 使用 Python 编写接口,实现新旧基础设施之间的对接。

Pieter Hintjens 是 ZeroMQ 项目发起者之一,他是个拥有 有趣视角和作品 的非凡人物。

准备

本教程中,需要:

Fedora 系统上的安装方法:

$ dnf install clang zeromq zeromq-devel python3 python3-zmq

Debian 和 Ubuntu 系统上的安装方法:

$ apt-get install clang libzmq5 libzmq3-dev python3 python3-zmq

如果有问题,参考对应项目的安装指南(上面附有链接)。

编写硬件接口库

因为这里针对的是个设想的场景,本教程虚构了包含两个函数的操作库:

  • fancyhw_init() 用来初始化(设想的)硬件
  • fancyhw_read_val() 用于返回从硬件读取的数据

将库的完整代码保存到文件 libfancyhw.h 中:

#ifndef LIBFANCYHW_H
#define LIBFANCYHW_H

#include <stdlib.h>
#include <stdint.h>

// This is the fictitious hardware interfacing library

void fancyhw_init(unsigned int init_param)
{
    srand(init_param);
}

int16_t fancyhw_read_val(void)
{
    return (int16_t)rand();
}

#endif

这个库可以模拟你要在不同语言实现的组件间交换的数据,中间有个随机数发生器。

设计 C 接口

下面从包含管理数据传输的库开始,逐步实现 C 接口。

需要的库

开始先加载必要的库(每个库的作用见代码注释):

// For printf()
#include <stdio.h>
// For EXIT_*
#include <stdlib.h>
// For memcpy()
#include <string.h>
// For sleep()
#include <unistd.h>

#include <zmq.h>

#include "libfancyhw.h"

必要的参数

定义 main 函数和后续过程中必要的参数:

int main(void)
{
    const unsigned int INIT_PARAM = 12345;
    const unsigned int REPETITIONS = 10;
    const unsigned int PACKET_SIZE = 16;
    const char *TOPIC = "fancyhw_data";

    ...

初始化

所有的库都需要初始化。虚构的那个只需要一个参数:

fancyhw_init(INIT_PARAM);

ZeroMQ 库需要实打实的初始化。首先,定义对象 context,它是用来管理全部的套接字的:

void *context = zmq_ctx_new();

if (!context)
{
    printf("ERROR: ZeroMQ error occurred during zmq_ctx_new(): %s\n", zmq_strerror(errno));

    return EXIT_FAILURE;
}

之后定义用来发送数据的套接字。ZeroMQ 支持若干种套接字,各有其用。使用 publish 套接字(也叫 PUB 套接字),可以复制消息并分发到多个接收端。这使得你可以让多个接收端接收同一个消息。没有接收者的消息将被丢弃(即不会入消息队列)。用法如下:

void *data_socket = zmq_socket(context, ZMQ_PUB);

套接字需要绑定到一个具体的地址,这样客户端就知道要连接哪里了。本例中,使用了 TCP 传输层(当然也有 其它选项,但 TCP 是不错的默认选择):

const int rb = zmq_bind(data_socket, "tcp://*:5555");

if (rb != 0)
{
    printf("ERROR: ZeroMQ error occurred during zmq_ctx_new(): %s\n", zmq_strerror(errno));

    return EXIT_FAILURE;
}

下一步, 计算一些后续要用到的值。 注意下面代码中的 TOPIC,因为 PUB 套接字发送的消息需要绑定一个主题。主题用于供接收者过滤消息:

const size_t topic_size = strlen(TOPIC);
const size_t envelope_size = topic_size + 1 + PACKET_SIZE * sizeof(int16_t);

printf("Topic: %s; topic size: %zu; Envelope size: %zu\n", TOPIC, topic_size, envelope_size);

发送消息

启动一个发送消息的循环,循环 REPETITIONS 次:

for (unsigned int i = 0; i < REPETITIONS; i++)
{
    ...

发送消息前,先填充一个长度为 PACKET_SIZE 的缓冲区。本库提供的是 16 个位的有符号整数。因为 C 语言中 int 类型占用空间大小与平台相关,不是确定的值,所以要使用指定宽度的 int 变量:

int16_t buffer[PACKET_SIZE];

for (unsigned int j = 0; j < PACKET_SIZE; j++)
{
    buffer[j] = fancyhw_read_val();
}

printf("Read %u data values\n", PACKET_SIZE);

消息的准备和发送的第一步是创建 ZeroMQ 消息,为消息分配必要的内存空间。空白的消息是用于封装要发送的数据的:

zmq_msg_t envelope;

const int rmi = zmq_msg_init_size(&envelope, envelope_size);
if (rmi != 0)
{
    printf("ERROR: ZeroMQ error occurred during zmq_msg_init_size(): %s\n", zmq_strerror(errno));

    zmq_msg_close(&envelope);

    break;
}

现在内存空间已分配,数据保存在 ZeroMQ 消息 “信封”中。函数 zmq_msg_data() 返回一个指向封装数据缓存区顶端的指针。第一部分是主题,之后是一个空格,最后是二进制数。主题和二进制数据之间的分隔符采用空格字符。需要遍历缓存区的话,使用类型转换和 指针算法。(感谢 C 语言,让事情变得直截了当。)做法如下:

memcpy(zmq_msg_data(&envelope), TOPIC, topic_size);
memcpy((void*)((char*)zmq_msg_data(&envelope) + topic_size), " ", 1);
memcpy((void*)((char*)zmq_msg_data(&envelope) + 1 + topic_size), buffer, PACKET_SIZE * sizeof(int16_t))

通过 data_socket 发送消息:

const size_t rs = zmq_msg_send(&envelope, data_socket, 0);
if (rs != envelope_size)
{
    printf("ERROR: ZeroMQ error occurred during zmq_msg_send(): %s\n", zmq_strerror(errno));

    zmq_msg_close(&envelope);

    break;
}

使用数据之前要先解除封装:

zmq_msg_close(&envelope);

printf("Message sent; i: %u, topic: %s\n", i, TOPIC);

清理

C 语言不提供 垃圾收集) 功能,用完之后记得要自己扫尾。发送消息之后结束程序之前,需要运行扫尾代码,释放分配的内存:

const int rc = zmq_close(data_socket);

if (rc != 0)
{
    printf("ERROR: ZeroMQ error occurred during zmq_close(): %s\n", zmq_strerror(errno));

    return EXIT_FAILURE;
}

const int rd = zmq_ctx_destroy(context);

if (rd != 0)
{
    printf("Error occurred during zmq_ctx_destroy(): %s\n", zmq_strerror(errno));

    return EXIT_FAILURE;
}

return EXIT_SUCCESS;

完整 C 代码

保存下面完整的接口代码到本地名为 hw_interface.c 的文件:

// For printf()
#include <stdio.h>
// For EXIT_*
#include <stdlib.h>
// For memcpy()
#include <string.h>
// For sleep()
#include <unistd.h>

#include <zmq.h>

#include "libfancyhw.h"

int main(void)
{
    const unsigned int INIT_PARAM = 12345;
    const unsigned int REPETITIONS = 10;
    const unsigned int PACKET_SIZE = 16;
    const char *TOPIC = "fancyhw_data";

    fancyhw_init(INIT_PARAM);

    void *context = zmq_ctx_new();

    if (!context)
    {
        printf("ERROR: ZeroMQ error occurred during zmq_ctx_new(): %s\n", zmq_strerror(errno));

        return EXIT_FAILURE;
    }

    void *data_socket = zmq_socket(context, ZMQ_PUB);

    const int rb = zmq_bind(data_socket, "tcp://*:5555");

    if (rb != 0)
    {
        printf("ERROR: ZeroMQ error occurred during zmq_ctx_new(): %s\n", zmq_strerror(errno));

        return EXIT_FAILURE;
    }

    const size_t topic_size = strlen(TOPIC);
    const size_t envelope_size = topic_size + 1 + PACKET_SIZE * sizeof(int16_t);

    printf("Topic: %s; topic size: %zu; Envelope size: %zu\n", TOPIC, topic_size, envelope_size);

    for (unsigned int i = 0; i < REPETITIONS; i++)
    {
        int16_t buffer[PACKET_SIZE];

        for (unsigned int j = 0; j < PACKET_SIZE; j++)
        {
            buffer[j] = fancyhw_read_val();
        }

        printf("Read %u data values\n", PACKET_SIZE);

        zmq_msg_t envelope;
   
        const int rmi = zmq_msg_init_size(&envelope, envelope_size);
        if (rmi != 0)
        {
            printf("ERROR: ZeroMQ error occurred during zmq_msg_init_size(): %s\n", zmq_strerror(errno));
   
            zmq_msg_close(&envelope);
   
            break;
        }
       
        memcpy(zmq_msg_data(&envelope), TOPIC, topic_size);

        memcpy((void*)((char*)zmq_msg_data(&envelope) + topic_size), " ", 1);

        memcpy((void*)((char*)zmq_msg_data(&envelope) + 1 + topic_size), buffer, PACKET_SIZE * sizeof(int16_t));
   
        const size_t rs = zmq_msg_send(&envelope, data_socket, 0);
        if (rs != envelope_size)
        {
            printf("ERROR: ZeroMQ error occurred during zmq_msg_send(): %s\n", zmq_strerror(errno));
   
            zmq_msg_close(&envelope);
   
            break;
        }
   
        zmq_msg_close(&envelope);

        printf("Message sent; i: %u, topic: %s\n", i, TOPIC);

        sleep(1);
    }

    const int rc = zmq_close(data_socket);

    if (rc != 0)
    {
        printf("ERROR: ZeroMQ error occurred during zmq_close(): %s\n", zmq_strerror(errno));

        return EXIT_FAILURE;
    }

    const int rd = zmq_ctx_destroy(context);

    if (rd != 0)
    {
        printf("Error occurred during zmq_ctx_destroy(): %s\n", zmq_strerror(errno));

        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

用如下命令编译:

$ clang -std=c99 -I. hw_interface.c -lzmq -o hw_interface

如果没有编译错误,你就可以运行这个接口了。贴心的是,ZeroMQ PUB 套接字可以在没有任何应用发送或接受数据的状态下运行,这简化了使用复杂度,因为这样不限制进程启动的次序。

运行该接口:

$ ./hw_interface
Topic: fancyhw_data; topic size: 12; Envelope size: 45
Read 16 data values
Message sent; i: 0, topic: fancyhw_data
Read 16 data values
Message sent; i: 1, topic: fancyhw_data
Read 16 data values
...
...

输出显示数据已经通过 ZeroMQ 完成发送,现在要做的是让一个程序去读数据。

编写 Python 数据处理器

现在已经准备好从 C 程序向 Python 应用传送数据了。

需要两个库帮助实现数据传输。首先是 ZeroMQ 的 Python 封装:

$ python3 -m pip install zmq

另一个就是 struct 库,用于解码二进制数据。这个库是 Python 标准库的一部分,所以不需要使用 pip 命令安装。

Python 程序的第一部分是导入这些库:

import zmq
import struct

重要参数

使用 ZeroMQ 时,只能向常量 TOPIC 定义相同的接收端发送消息:

topic = "fancyhw_data".encode('ascii')

print("Reading messages with topic: {}".format(topic))

初始化

下一步,初始化上下文和套接字。使用 subscribe 套接字(也称为 SUB 套接字),它是 PUB 套接字的天生伴侣。这个套接字发送时也需要匹配主题。

with zmq.Context() as context:
    socket = context.socket(zmq.SUB)

    socket.connect("tcp://127.0.0.1:5555")
    socket.setsockopt(zmq.SUBSCRIBE, topic)

    i = 0

    ...

接收消息

启动一个无限循环,等待接收发送到 SUB 套接字的新消息。这个循环会在你按下 Ctrl+C 组合键或者内部发生错误时终止:

    try:
        while True:

            ... # we will fill this in next

    except KeyboardInterrupt:
        socket.close()
    except Exception as error:
        print("ERROR: {}".format(error))
        socket.close()

这个循环等待 recv() 方法获取的新消息,然后将接收到的内容从第一个空格字符处分割开,从而得到主题:

binary_topic, data_buffer = socket.recv().split(b' ', 1)

解码消息

Python 此时尚不知道主题是个字符串,使用标准 ASCII 编解码器进行解码:

topic = binary_topic.decode(encoding = 'ascii')

print("Message {:d}:".format(i))
print("\ttopic: '{}'".format(topic))

下一步就是使用 struct 库读取二进制数据,它可以将二进制数据段转换为明确的数值。首先,计算数据包中数值的组数。本例中使用的 16 个位的有符号整数对应的是 struct 格式字符 中的 h

packet_size = len(data_buffer) // struct.calcsize("h")

print("\tpacket size: {:d}".format(packet_size))

知道数据包中有多少组数据后,就可以通过构建一个包含数据组数和数据类型的字符串,来定义格式了(比如“16h”):

struct_format = "{:d}h".format(packet_size)

将二进制数据串转换为可直接打印的一系列数字:

data = struct.unpack(struct_format, data_buffer)

print("\tdata: {}".format(data))

完整 Python 代码

下面是 Python 实现的完整的接收端:

#! /usr/bin/env python3

import zmq
import struct

topic = "fancyhw_data".encode('ascii')

print("Reading messages with topic: {}".format(topic))

with zmq.Context() as context:
    socket = context.socket(zmq.SUB)

    socket.connect("tcp://127.0.0.1:5555")
    socket.setsockopt(zmq.SUBSCRIBE, topic)

    i = 0

    try:
        while True:
            binary_topic, data_buffer = socket.recv().split(b' ', 1)

            topic = binary_topic.decode(encoding = 'ascii')

            print("Message {:d}:".format(i))
            print("\ttopic: '{}'".format(topic))

            packet_size = len(data_buffer) // struct.calcsize("h")

            print("\tpacket size: {:d}".format(packet_size))

            struct_format = "{:d}h".format(packet_size)

            data = struct.unpack(struct_format, data_buffer)

            print("\tdata: {}".format(data))

            i += 1

    except KeyboardInterrupt:
        socket.close()
    except Exception as error:
        print("ERROR: {}".format(error))
        socket.close()

将上面的内容保存到名为 online_analysis.py 的文件。Python 代码不需要编译,你可以直接运行它。

运行输出如下:

$ ./online_analysis.py
Reading messages with topic: b'fancyhw_data'
Message 0:
        topic: 'fancyhw_data'
        packet size: 16
        data: (20946, -23616, 9865, 31416, -15911, -10845, -5332, 25662, 10955, -32501, -18717, -24490, -16511, -28861, 24205, 26568)
Message 1:
        topic: 'fancyhw_data'
        packet size: 16
        data: (12505, 31355, 14083, -19654, -9141, 14532, -25591, 31203, 10428, -25564, -732, -7979, 9529, -27982, 29610, 30475)
...
...

小结

本教程介绍了一种新方式,实现从基于 C 的硬件接口收集数据,并分发到基于 Python 的基础设施的功能。借此可以获取数据供后续分析,或者转送到任意数量的接收端去。它采用了一个消息库实现数据在发送者和处理者之间的传送,来取代同样功能规模庞大的软件。

本教程还引出了我称之为“软件粒度”的概念,换言之,就是将软件细分为更小的部分。这种做法的优点之一就是,使得同时采用不同的编程语言实现最简接口作为不同部分之间沟通的组件成为可能。

实践中,这种设计使得软件工程师能以更独立、合作更高效的方式做事。不同的团队可以专注于数据分析的不同方面,可以选择自己中意的实现工具。这种做法的另一个优点是实现了零代价的并行,因为所有的进程都可以并行运行。ZeroMQ 消息库 是个令人赞叹的软件,使用它可以让工作大大简化。


via: https://opensource.com/article/20/3/zeromq-c-python

作者:Cristiano L. Fontana 选题:lujun9972 译者:silentdawn-zz 校对:wxy

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

使用 PDNS 为你的项目提供稳定可靠的域名系统(DNS)服务器。

几个月前,我们接到了一个要求,为一个新项目提供一个稳定可靠的域名系统(DNS)服务器。该项目使用容器进行自动部署,每个新环境都会生成唯一的随机 URL。在对可能的方案进行了大量研究之后,我们决定尝试一下 PowerDNS(PDNS)。

一开始,我们发现 PowerDNS 在所有主流 Linux 发行版中都得到了支持,它采用 GPL 许可,且仓库保持更新。我们还在官方网站上发现了整洁、组织良好的文档,以及大量来自真正喜欢和使用该产品的人在网络上的使用方法。看了一些并学习了一些基本命令之后,安装了 PDNS,启动并运行,我们的旅程开始了。

数据库驱动

PowerDNS 将记录保存在 SQL 数据库中。这对我们来说是新变化,不必使用文本文件来保存记录是一个不错的更改。我们选择 MariaDB 作为首选的强大工具,由于有大量的正确地设置来安装名称服务器的信息,我们可以完美地设置和加固我们的数据库。

简单配置

其次使我们感兴趣的是 PDNS 的所有功能都在配置文件中。pdns.conf 有许多选项,你可以通过添加或删除 号来启用或禁用这些选项。这真是太神奇了,因为它使我们有机会将这项新的服务集成到我们现有的基础架构中,并且只有我们想要的功能,不多也不少。一个简单的例子:

谁可以访问你的网络服务器?

webserver-allow-from=172.10.0.1,172.10.1.2

我可以转发基于域的请求吗?当然!

forward-zones=mylocal.io=127.0.0.1:5300
forward-zones+=example.com=172.10.0.5:53
forward-zones+=lucky.tech=172.10.1.5:53

包含 API

我们可以使用配置文件进行激活 API 服务,解决了我们开发团队的第一个需求,让我们见识到了 PDNS 的强大。这个功能让我们通过发送请求,简单、干净地创建、修改或删除 DNS 服务器中的记录。

这个 API 有一些基本的安全性参数,因此,只需几步,你就可以基于 IP 地址和预共享密钥验证的组合来控制谁有权与名称服务器进行交互。这是配置文件的样子:

api=yes
api-key=lkjdsfpoiernf
webserver-allow-from=172.10.7.13,172.10.7.5

日志

在日志方面,PDNS 做得非常出色。你可以使用日志文件和一个简单的内置 Web 服务器来监控服务器并查看计算机的运行状况。你可以使用浏览器查看服务器不同类型的统计信息,例如 CPU 使用率和收到的 DNS 查询。这非常有价值。例如,我们能够检测到一些“不太健康”的 PC,它们正在向我们的服务器发送与恶意流量相关的站点的 DNS 请求。深入查看日志后,我们可以看到流量来自何处,并对这些 PC 进行清理操作。

其他功能

这只是你使用 PowerDNS 可以做的所有事情的一点点。它还有更多的功能。它是一个拥有很多功能和特性的完整名称服务器,因此值得一试。

目前,我们尚未部署 DNSSEC,但似乎只需点击一下即可将其快速投入生产环境。另外,在将递归服务与名称服务器分离时,PowerDNS 有个不错的方法。我了解到它还支持 DNS RPZ(响应策略区域),并且还提供了非常不错且设计良好的前端,可让你使用 Web 浏览器来管理服务器,如下图。

 title=

信不信由你,你只需花费几个小时了解 PDNS,就可以大大提高你对 DNS 和 IT 操作的了解。


via: https://opensource.com/article/20/5/powerdns

作者:Jonathan Garrido 选题:lujun9972 译者:geekpi 校对:wxy

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

Altair 作为一个 Python 数据制图库,提供了优雅的接口及自有的绘图语言。

Python 中的 绘图库 提供了呈现数据的多种方式,可以满足你不同的偏好,如灵活性、布局、易用性,或者特殊的风格。

和其它方式相比,我发现,Altair 提供的是一种不同的解决方案,且总体而言使用起来更为简单。得益于声明式的绘图语言 Vega,Altair 拥有一套优雅的接口,可以直接定义要绘的图应该是什么样子,而不是通过写一大堆循环和条件判断去一步步构建。

绘图流程

我通过绘制同一个多柱状图比较了多个 Python 绘图库的差异。正式开始之前,你需要将你的 Python 环境调整到能运行下面代码的状态。具体就是:

  • 安装最新版的 Python( LinuxMacWindows 系统下的安装方法)
  • 确认该版本 Python 可以运行本教程所使用的库

演示用数据可从网络下载,并且可以用 pandas 直接导入:

import pandas as pd
df = pd.read_csv('https://anvil.works/blog/img/plotting-in-python/uk-election-results.csv')

准备开始吧。为了做个比较,先看下面这个用 Matplotlib 做的图:

 title=

使用 Matplotlib 需要 16 行代码,图柱的位置需要自己计算。

使用 Altair 绘制相似的图,代码如下:

    import altair as alt

    chart = alt.Chart(df).mark_bar().encode(
        x='party',
        y='seats',
        column='year',
        color='party',
    )

    chart.save('altair-elections.html')

真是简洁多了!与 Seaborn 类似,Altair 所用数据的组织形式是每个变量一列(即 数据列 )。这种方式下可以将每个变量映射到图的一个属性上 —— Altair 称之为“通道”。在上例中,我们期望每个 “党派” 在 x 轴上显示为一组图柱, 其 “席位” 显示在 y 轴,且将图柱按照 “年份” 分开为 “列”。我们还想根据 “党派” 给图柱使用不同的 “颜色”。用语言表述需求的话就是上面这个样子,而这也正是代码所要表述的!

现在把图画出来:

 title=

调整样式

这和我们期待的效果有点接近了。与 Matplotlib 方案相比,主要区别在于 Altair 方案中,每个 year 组显示的时候,内部之间都有个小空白 —— 这不是问题,这只是 Altair 多柱状图显示的一个特性。

所以说呢,还需要对显示样式再做一些改进。

非整形数据

两个不是整数的年份名称(Feb 1974Oct 1974)显示为 NaN 了。这可以通过将年份数值 year 转换为字符串来解决:

    df['year'] = df['year'].astype(str)

指定数据排序方法

还需要让 Altair 知道如何对数据进行排序。Altair 允许通过传给它一个 Column 对象,来设定 Column 通道的更多细节。现在让 Altair 按照数据在数据集中出现的顺序排列:

    chart = alt.Chart(df).mark_bar().encode(
        # ...
        column=alt.Column('year', sort=list(df['year']), title=None),
        # ...
    )

移除坐标轴标签

我们通过设置 title=None 移除了图顶的 "year" 标签。下面再一处每列数据的 "party" 标签:

    chart = alt.Chart(df).mark_bar().encode(
        x=alt.X('party', title=None),
        # ...
    )

指定颜色图

最后,我们还想自己指定图柱的颜色。Altair 允许建立 domain 中数值与 range 中颜色的映射来实现所需功能,太贴心了:

    cmap = {
        'Conservative': '#0343df',
        'Labour': '#e50000',
        'Liberal': '#ffff14',
        'Others': '#929591',
    }

    chart = alt.Chart(df).mark_bar().encode(
        # ...
        color=alt.Color('party', scale=alt.Scale(domain=list(cmap.keys()), range=list(cmap.values())))
    )

样式调整后的最终代码

应用上述样式调整之后,代码看起来不那么悦目了,但我们仍然是用声明的方式实现的,这正是 Altair 如此有弹性的原因所在。实现过程中,仍然是使用的异于显示数据的独立变量来分离图中不同属性的,而不是像在 Matplotlib 中那样直接对显示数据做复杂的操作。唯一的不同是,我们的变量名字封装在类似 alt.X() 的对象中,从而实现对显示效果的控制:

    import altair as alt
    from votes import long as df

    cmap = {
        'Conservative': '#0343df',
        'Labour': '#e50000',
        'Liberal': '#ffff14',
        'Others': '#929591',
    }

    df['year'] = df['year'].astype(str)

    # We're still assigning, e.g. 'party' to x, but now we've wrapped it
    # in alt.X in order to specify its styling
    chart = alt.Chart(df).mark_bar().encode(
        x=alt.X('party', title=None),
        y='seats',
        column=alt.Column('year', sort=list(df['year']), title=None),
        color=alt.Color('party', scale=alt.Scale(domain=list(cmap.keys()), range=list(cmap.values())))
    )

    chart.save('altair-elections.html')

现在与 Matplotlib 方案扯平了,代码数量达到了 16 行!

下图是使用我们的样式调整方案之后的 Altair 效果图:

 title=

结论

尽管在代码数量上,使用 Altair 绘图没有表现出优势,但它的声明式绘图语言使得对图层的操控更为精密,这是我比较欣赏的。Altair 还提供了清晰而独立的方式来调校显示样式,这使得 相关代码与绘图的代码块分离开来。Altair 确实是使用 Python 绘图时又一个很棒的工具库。

本文首次发布于 这里,蒙允编辑后再次发布。


via: https://opensource.com/article/20/6/altair-python

作者:Shaun Taylor-Morgan 选题:lujun9972 译者:silentdawn-zz 校对:wxy

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

UFCS 能让你能够编写自然的可重用代码而不会牺牲便利性。

早在 2017 年,我就写过为什么 D 语言是开发的绝佳选择的文章。但是 D 语言中有一个出色的功能我没有充分的展开介绍: 通用函数调用语法 Universal Function Call Syntax (UFCS)。UFCS 是 D 语言中的一种语法糖,它可以在类型(字符串、数字、布尔值等)上链接任何常规函数,就像该类型的成员函数一样。

如果你尚未安装 D 语言,请安装 D 语言编译器,以便你可以自己运行 D 代码

看一下以下示例代码:

// file: ufcs_demo.d

module ufcs_demo;

import std.stdio : writeln;

int[] evenNumbers(int[] numbers)
{
    import std.array : array;
    import std.algorithm : filter;

    return numbers.filter!(n => n % 2 == 0).array;
}

void main()
{
    writeln(evenNumbers([1, 2, 3, 4]));
}

使用你喜欢的 D 语言编译器进行编译,查看这个简单示例应用做了什么:

$ dmd ufcs_demo.d
$ ./ufcs_demo
[2, 4]

但是,使用作为 D 语言的内置功能的 UFCS ,你还可以自然方式编写代码:

...
writeln([1, 2, 3, 4].evenNumbers());
...

或完全删除现在多余的括号,使 evenNumbers 看起来像是一个属性:

...
writeln([1, 2, 3, 4].evenNumbers); // prints 2, 4
...

因此,完整的代码现在变为:

// file: ufcs_demo.d

module ufcs_demo;

import std.stdio : writeln;

int[] evenNumbers(int[] numbers)
{
    import std.array : array;
    import std.algorithm : filter;

    return numbers.filter!(n => n % 2 == 0).array;
}

void main()
{
    writeln([1, 2, 3, 4].evenNumbers);
}

使用你最喜欢的 D 语言编译器进行编译,然后尝试一下。 如预期的那样,它产生相同的输出:

$ dmd ufcs_demo.d
$ ./ufcs_demo
[2, 4]

在编译过程中,编译器自动地将数组作为函数的第一个参数。这是一个常规模式,使得使用 D 语言成为一种乐趣,因此,它与你自然而然考虑代码的感觉非常相似。结果就是函数式编程。

你可能会猜出这打印的是什么:

//file: cool.d
import std.stdio : writeln;
import std.uni : asLowerCase, asCapitalized;

void main()
{
    string mySentence = "D IS COOL";
    writeln(mySentence.asLowerCase.asCapitalized);
}

确认一下:

$ dmd cool.d
$ ./cool
D is cool

结合其他 D 语言的功能,UFCS 使你能够编写可重用的代码,并在不牺牲便利性的情况下自然地进行编写。

是时候尝试 D 语言了

就像我之前写的那样,D 语言是一种很棒的开发语言。从 D 语言的下载页面可以很容易地进行安装,因此请下载编译器,查看示例,并亲自体验 D 语言。


via: https://opensource.com/article/20/7/d-programming

作者:Lawrence Aberba 选题:lujun9972 译者:geekpi 校对:wxy

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