python传递数据_使用此消息传递库在C和Python之间共享数据

本文介绍如何使用ZeroMQ库在C编写的硬件接口和Python处理程序之间交换数据。通过创建C接口发布硬件读取的数据,然后用Python编写数据处理器订阅并解码这些消息,实现跨语言通信。
摘要由CSDN通过智能技术生成

python传递数据

当我被要求做一项使我的脊椎发抖的任务时,我曾有一段时间担任软件工程师。 这样的时刻是我不得不在一些需要C的新硬件基础设施和主要是Python的云基础设施之间编写接口。

一种策略可能是用C编写扩展 ,Python设计支持该扩展 。 快速浏览文档说明这将意味着编写大量的C。在某些情况下这可能会很好,但是我不喜欢这样做。 另一种策略是将这两个任务放在单独的进程中,并使用ZeroMQ消息传递库在两者之间交换消息。

当我在发现ZeroMQ之前经历了这种情况时,我经历了扩展编写路径。 并没有那么糟糕,但是却非常耗时且令人费解。 如今,为了避免这种情况,我将系统细分为独立的进程,这些进程通过通过通信套接字发送的消息交换信息。 通过这种方法,几种编程语言可以共存,并且每个过程都更简单,因此更容易调试。

ZeroMQ提供了更简单的过程:

  1. 用C编写一个小的填充程序,该填充程序从硬件读取数据并将发现的任何内容作为消息发送。
  2. 在新基础架构和现有基础架构之间编写Python接口。

ZeroMQ项目的创始人之一是Pieter Hintjens ,他是一位杰出的人物,有着有趣的观点和著作

先决条件

对于本教程,您将需要:

使用以下命令在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"

重要参数

定义程序其余部分所需的主要功能和重要参数:


   
   
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库需要一些实际的初始化。 首先,定义一个上下文 —一个管理所有套接字的对象:


   
   
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支持几种类型的套接字,每种都有其应用程序。 使用发布套接字(也称为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 ;
}

接下来,计算一些以后将需要的有用值。 注意下面代码中的TOPICPUB套接字需要一个主题与其发送的消息相关联。 接收者可以使用主题来过滤消息:


   
   
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应用程序了。

图书馆

您需要两个库来帮助传输数据。 首先,您需要Python中的ZeroMQ绑定:

 $  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 ) )

初始化

接下来,初始化上下文和套接字。 使用订阅套接字(也称为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 ( " \t topic: '{}'" . format ( topic ) )

下一步是使用struct库读取二进制数据,该库可以将无形状的二进制blob转换为有效值。 首先,计算存储在数据包中的值的数量。 本示例使用16位带符号整数,它们与struct 格式中的“ h”相对应:


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

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

通过知道数据包中有多少个值,您可以通过准备一个带有值的数量及其类型(例如,“ 16h ”)的字符串来定义格式:

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

将该二进制blob转换为可以立即打印的一系列数字:


   
   
data = struct . unpack ( struct_format , data_buffer )

print ( " \t data: {}" . 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 ( " \t topic: '{}'" . format ( topic ) )

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

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

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

            data = struct . unpack ( struct_format , data_buffer )

            print ( " \t data: {}" . 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 )
...
...

结论

本教程还增加了我所谓的“软件粒度”。 换句话说,它将软件细分为较小的单元。 这种策略的好处之一是可以同时使用不同的编程语言,而最少的接口充当了它们之间的垫片。

实际上,这种设计允许软件工程师更加协作和独立地工作。 不同的团队可能会在分析的不同步骤上工作,选择他们喜欢的工具。 另一个好处是免费提供了并行性,因为所有进程都可以并行运行。 ZeroMQ消息传递库是一款了不起的软件,它使所有这些工作变得更加容易。

翻译自: https://opensource.com/article/20/3/zeromq-c-python

python传递数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值