C++:如何使我的Python程序提供的功能对在同一台或其他计算机上运行的其他语言编写的程序可用?

shyt4zoc  于 2021-09-08  发布在  Java
关注(0)|答案(1)|浏览(373)

我创建了一个用python编写的系统,它提供了各种硬件控制、数据采集和处理功能。我希望这个系统可以从Python或其他语言编写的程序访问,如Lua、Matlab、八度、C++和其他可能的。此外,这些程序可以在该计算机上运行,也可以在通过网络连接的另一台计算机上运行。
我知道存在各种类似python的rpc库
https://pypi.org/project/simple-rpc/ (不提供与多种语言的轻松接口)
冰https://zeroc.com/products/ice (对于我的简单应用程序来说太大了)
是否有任何简单轻量级的解决方案可以从其他语言编写的程序中远程调用python函数?

vltsax25

vltsax251#

我在2012年开发了一个类似的系统,python函数的参数和返回值通过messagepack传递。这样的解决方案可以与用各种语言编写的程序进行通信。它是在usenet alt.sources组中发布的https://groups.google.com/g/alt.sources/c/qaspxjkkius (也可在funet存档中获得)http://ftp.funet.fi/pub/archive/alt.sources/2722.gz ). 现在我用zeromq库对其进行了补充,该库提供了与运行在同一台机器或远程机器上的程序的有效通信。
系统的核心是用python编写的服务器:


# !/usr/bin/python3

"""
   The code below implements a simple ZeroMQ and MessagePack RPC server.
   Written by Wojciech M. Zabolotny, wzab<at>ise.pw.edu.pl or wzab01<at>gmail.com
   13.04.2021

   The code is based on a post: "Simple object based RPC for Python",
   https://groups.google.com/g/alt.sources/c/QasPxJkKIUs/m/An1JnLooNCcJ
   https://www.funet.fi/pub/archive/alt.sources/2722.gz

   This code is published as PUBLIC DOMAIN  or under 
   Creative Commons Zero v1.0 Universal license (whichever better suits your needs).
"""
import time
import zmq

import msgpack as mp
import traceback
import os

# Functions which process requests

def remote_mult(a,b):
    return a*b

def remote_div(a,b):
    print(a,b,a/b)
    return a/b

def cat_file(fname):
    f=open(fname,"rb")
    return f.read()

# Table of functions

func={
  'mult':remote_mult,
  'div':remote_div,
  'file':cat_file,
}

def handle(msg):
    try:
        obj = mp.unpackb(msg)
        if len(obj) != 2:
            raise Exception("Wrong number of RPC objects, should be 2: name and arguments")
        if isinstance(obj[1],tuple) or isinstance(obj[1],list):
            res=func[obj[0]](*obj[1])
        elif isinstance(obj[1],dict):
            res=func[obj[0]](**obj[1])
        else:
            raise Exception("Wrong type of arguments in RPC, should be list, tuple or dictionary")
        res = ("OK", res)
    except Exception:
        res=("error", traceback.format_exc())
    return mp.packb(res)

if __name__ == "__main__":
    context = zmq.Context()
    socket = context.socket(zmq.REP)
    # Create the server, binding to localhost on port 9999
    socket.bind("tcp://*:9999")
    while True:
        msg = socket.recv()
        res = handle(msg)
        socket.send(res)

服务器可以发布具有不同参数的不同函数。这些参数可以作为位置参数(然后它们应该以元组的形式传递)或关键字参数(然后它们应该作为Map/字典传递)。
下面给出了使用这些函数的python客户端示例:


# !/usr/bin/python3

"""
   The code below implements a simple ZeroMQ and MessagePack RPC client.
   Written by Wojciech M. Zabolotny, wzab<at>ise.pw.edu.pl or wzab01<at>gmail.com
   13.04.2021

   The code is based on a post: "Simple object based RPC for Python",
   https://groups.google.com/g/alt.sources/c/QasPxJkKIUs/m/An1JnLooNCcJ
   https://www.funet.fi/pub/archive/alt.sources/2722.gz

   This code is published as PUBLIC DOMAIN  or under 
   Creative Commons Zero v1.0 Universal license (whichever better suits your needs).
"""
import socket
import sys
import msgpack as p
import zmq

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
objects=[
    ["mult",(4,5)],
    ["mult",{"a":7,"b":8}],
    ["div",{"a":9,"b":4}],
    ["file",("/etc/passwd",)],
    ["file",("/etc/non_existing_file",)],
]

context = zmq.Context()

socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:9999")    

for obj in objects:
    socket.send(p.packb(obj))
    #  Get the reply.
    msg = socket.recv()
    resp = p.unpackb(msg)
    print("Received reply", resp)

以下是用其他语言编写的客户端。
在卢阿:

-- Demonstrator of the communication with simple Python RPC server from Lua
-- Written by Wojciech M. Zabołotny (wzab01<at>gmail.com or wzab<at>ise.pw.edu.pl)
-- Copyright: This program is released into the public domain or under
-- Creative Commons Zero v1.0 Universal license (whichever better suits your needs).
local zmq = require "lzmq"
--require "utils"
local mp = require "mpack"
--print_version(zmq)
local pr = require "pl.pretty"
context  = assert(zmq.context())
rpcsrv = assert(context:socket (zmq.REQ))
assert(rpcsrv:connect("tcp://localhost:9999"))

function rpc(params)
    local req=mp.pack(test)
    rpcsrv:send(req)
    local rcv=rpcsrv:recv()
    local res=mp.unpack(rcv)
    return res  
end

test = {"file",{"/etc/passwd"}}
local res = rpc(test)
pr.dump(res)
test = {"mult",{7,8}}
res = rpc(test)
pr.dump(res)
test = {"div",{b=4.0,a=9.0}}
res = rpc(test)
pr.dump(res)
-- The above works, but 9/4 is printed as 2.
print(res[2])
test = {"div",{a=4.0,b=9.0}}
res = rpc(test)
pr.dump(res)
-- The above works, but 4/9 is printed as 0.
print(res[2])

八度

% Demonstrator of the communication with simple Python RPC server from Octave
% Written by Wojciech M. Zabołotny (wzab01<at>gmail.com or wzab<at>ise.pw.edu.pl)
% Copyright: This program is released into the public domain.
pkg load jsonlab
pkg load zeromq

srv = zmq_socket (ZMQ_REQ);
zmq_connect (srv, "tcp://localhost:9999");

function res = rpc(req,fname,fargs,maxlen=10000)
  x={fname, fargs};
  a=savemsgpack(x);
  zmq_send(req,a);
  w=zmq_recv(req,maxlen);
  y=loadmsgpack(char(w));
  if strcmp(char(y{1}),"OK")
     res = y{2};
  end
  if strcmp(char(y{1}),"error")
     error(char(y{2}));
  end
endfunction

res = rpc(srv,"mult",struct("a",13,"b",20));
res
res = rpc(srv,"mult",{17,3});
res
res = rpc(srv,"file",{"/etc/passwd"});
char(res')

最后在C++中

// Demonstrator of the communication with simple Python RPC server from C++
// Written by Wojciech M. Zabołotny (wzab01<at>gmail.com or wzab<at>ise.pw.edu.pl)
// Copyright: This program is released into the public domain or under
// Creative Commons Zero v1.0 Universal license (whichever better suits your needs).

# include <string>

# include <zmq.hpp>

# include <iostream>

# include <msgpack.hpp>

msgpack::object_handle rpc(zmq::socket_t &sock, auto req)
{
   std::size_t offset = 0;
   std::stringstream rstr;
   msgpack::pack(rstr,req);
   zmq::message_t msg(rstr.str());
   sock.send(msg,zmq::send_flags::none);
   auto res = sock.recv(msg, zmq::recv_flags::none);
   auto oh = msgpack::unpack((const char *)msg.data(),msg.size(),offset);
   return oh;
}

int main(void) {
   zmq::context_t ctx;
   zmq::socket_t sock(ctx, zmq::socket_type::req);
   sock.connect("tcp://localhost:9999");
   msgpack::object_handle res;
   res = rpc(sock,std::tuple<std::string, std::array<int,2>>({"mult", {7, 8}}));
   std::cout << res.get() << std::endl;
   res = rpc(sock,std::tuple<std::string, std::map<std::string,int>>({"div", {{"a",8},{"b",7}}}));
   std::cout << res.get() << std::endl;
   res = rpc(sock,std::tuple<std::string, std::map<std::string,int>>({"div", {{"b",8},{"a",7}}}));
   std::cout << res.get() << std::endl;
   res = rpc(sock,std::tuple<std::string, std::tuple<std::string>>({ "file", {"/etc/passwd"}}));
   std::cout << res.get() << std::endl;
}

上面的C++代码必须用 -fconcepts-ts 选项,例如,如下所示:

c++ obj_rpc_cli.cc -o obj_rpc_cli -fconcepts-ts -g -lzmq -lmsgpackc

解决方案实际上是最小的。github存储库中提供了维护的版本:https://gitlab.com/wzab/python-versatile-rpc

相关问题