Python 与 C++ 的进程通信

发布于:2022-11-09 ⋅ 阅读:(853) ⋅ 点赞:(0)

Python与C++的进程通信

因近期工作需要,需要用python解析数据发送到c++程序端做处理,然后用Python可视化c++端的结果,故汇总了一些python与c++进程间通信的方式。

代码都是在Ubuntu上开发的,在windows上可能不通用。都是些简单的示例,可以在这个基础上扩展。都是些简单代码,复杂的实现就不讨论了…

同时,这些通信方式在同语言之间的进程也是可以通用的。另外,工程文件源码在文章末尾,记得点赞收藏。

1. 管道通信

最简单的做法,本地新建两个文件,相互读写,但需要顺序性,所以用到管道的阻塞特性。

  • 可以先参考Linux管道读写阻塞,大致是管道可以理解为文件,写的时候读会阻塞,读的时候写会阻塞。

  • 定义python程序为node1,c++程序为node2。node1往node2发送信息为管道1,node2往node1发送信息为管道2。

  • 执行的顺序如下,node1写入管道1,node2读取管道1,node2写入管道2,node1读取管道1。因为管道的阻塞性,因此可以实现该顺序的通信。

  • 示例如下,放了跨语言通信的demo,另外c++和python单语言进程间通信的代码也写了,见文章末尾。

fifo

  • python创建管道,以及node1。数据是可以用json传输的,方便两语言之间的数据读取,这里的简单示例就只用了简单的字符串。
import os
import time


# node1
def TestIFIO():
    input_file = "/tmp/node2_to_node1.tmp"
    output_file = "/tmp/node1_to_node2.tmp"

    # 1.create fifo
    if not os.path.exists(input_file):
        os.mkfifo(input_file, 0o666)
    if not os.path.exists(output_file):
        os.mkfifo(output_file, 0o666)

    # 2.open pipe
    print('init write pipe: ' + output_file)
    fout = os.open(output_file, os.O_WRONLY)
    print('init read pipe:  ' + input_file)
    fin = os.open(input_file, os.O_RDONLY)

    # 3.write and read data
    send_str = "How are you?"
    while True:
        try:
            os.write(fout, str.encode(send_str))
        except:
            print("node2 closed, exit!")
            break
        print("send: ", send_str)
        
        recv_str = os.read(fin, 1024).decode()[:-1]
        print("recv: ", recv_str)
        time.sleep(0.5)

    os.close(fin)
    os.close(fout)


if __name__ == '__main__':
    TestIFIO()

  • c++读取管道,创建node2
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <cstring>
#include <sstream>

#define INPUT_PATH_NAME "/tmp/node1_to_node2.tmp"
#define OUTPUT_PATH_NAME "/tmp/node2_to_node1.tmp"

// node2
int main() {
  int fin = open(INPUT_PATH_NAME, O_RDONLY);
  int fout = open(OUTPUT_PATH_NAME, O_WRONLY);

  char buffer[1024]; // 定义接受的文件大小,可以设大点,1024*1024*n
  while (true) {
    memset(buffer, 0, sizeof(buffer));
    if (read(fin, buffer, sizeof(buffer)) <= 0) {
      printf("node1 closed, exit!\n");
      break;
    }
    printf("receive: %s\n", buffer);

    std::stringstream ss;
    ss << "I'm fine, thx!";
    if (write(fout, ss.str().c_str(), ss.str().length() + 1) <= 0) {
      break;
    }
    printf("send   : %s\n", ss.str().c_str());
  }
  close(fin);
  close(fout);
  return 0;
}

2. Socket通信(推荐)

个人觉得还是这个好用方便,主要跟文件操作打交道的我都不喜欢

socket用于网络服务,把复杂的TCP/IP协议封装,具体实现几次握手不用了解了(想了解可以百度了,这里不多做介绍),只需要调用接口就可以实现数据通信了.

socket分为服务端客户端,先启动服务端后,客户端向服务端发送数据,服务端接受数据并返回消息给客户端。

示例使用安全可靠的TCP协议SOCK_STREAM

socket比较方便,所以c++和python我分别都写了客户端和服务端,都比较简单,可以交叉通信,也可以同语言通信,所以总共可以四种组合通信,满足服务端和客户端的组合即可。

socket

  • python客户端
import socket
import time


def TestClient():
    server_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_client.connect(("localhost", 8888))

    info = "How are you?"
    while True:
        server_client.send(bytes(info, encoding='utf-8'))
        print("send   : ", info)
        recv_str = server_client.recv(1024)
        if not recv_str:
            break
        print("receive: ", recv_str.decode("utf-8"))
        time.sleep(0.5)

    server_client.close()
    print("server end, exit!")
    exit()


if __name__ == '__main__':
    TestClient()

  • python服务端
import socket
import time


def TestServer():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind(("localhost", 8888))
    server.listen(5)
    connection, address = server.accept()
    print(connection, address)
    while True:
        recv_str = connection.recv(1024)
        recv_str = recv_str.decode("ascii")
        if not recv_str:
            break
        print("receive:{}".format(recv_str))

        send_str = "I'm fine, thx!"
        connection.send(bytes(send_str, encoding="ascii"))
        print("send:   {}".format(send_str))
        time.sleep(0.5)

    connection.close()
    server.close()
    print("client end, exit!")
    exit()


if __name__ == '__main__':
    TestServer()

  • c++ 客户端

    其中每个函数都会有返回值,示例简单就没做异常判断了,可以点开每个函数定义查看。

#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cstring>
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>


int main() {
  // 1. 创建客户端,并连接到服务端
  int sock_client = socket(AF_INET, SOCK_STREAM, 0);
  sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(8888);
  connect(sock_client, (sockaddr*)&server_addr, sizeof(sockaddr));

  // 2. 发送数据,并接受服务端数据
  char* send_info = {"How are you?"};
  while(1) {
    send(sock_client, send_info, strlen(send_info) + 1, 0);
    printf("send:    %s\n", send_info);

    char recv_info[50];
    recv(sock_client, recv_info, strlen(recv_info), 0);
    printf("receive: %s\n", recv_info);
  }

  // 3. 关闭客户端
  close(sock_client);
  return 0;
}

  • c++ 客户端
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cstring>
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>


int main() {
  // 1. 创建客户端,并连接到服务端
  int sock_client = socket(AF_INET, SOCK_STREAM, 0);
  sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(8888);
  connect(sock_client, (sockaddr*)&server_addr, sizeof(sockaddr));

  // 2. 发送数据,并接受服务端数据
  char* send_info = {"How are you?"};
  while(1) {
    send(sock_client, send_info, strlen(send_info) + 1, 0);
    printf("send:    %s\n", send_info);

    char recv_info[50];
    recv(sock_client, recv_info, strlen(recv_info), 0);
    printf("receive: %s\n", recv_info);
  }

  // 3. 关闭客户端
  close(sock_client);
  return 0;
}

3. 共享内存通信

共享内存的实现有些麻烦,同样也是开辟一块共有内存进行读写,速度也很快,正常读写没啥问题,但是通信的话存在同步性的问题,就是没有管道的阻塞性。

  • 会创建数据,读完了数据依旧存在。
  • 没法保证:写了只能读,读了之后才能写。所以需要自己添加同步机制:加互斥锁,或者加标志位。
  • 共享内存还是偏底层基础了,没有上面两个方便,但能在这基础上开发出很厉害的东西。

基于mmap

mmap(Memory-mapped file support),内存映射文件支持,详细见python标准库

内存映射文件在Unix和Windows上是不同的,但本质都需要提供一个打开的文件来提供文件描述符以进行更新。

  • 因为没有同步机制,所以两个文件收发并不是同时的,所以并没有实现真正的通讯,只能算交互。但是代码还是放上来吧,记录下。(同步机制没实现,主要是python不咋会用-.- c++加个互斥锁就解决了)

shell

  • python-node1
import mmap
import contextlib
import time

with open("node1.dat", "w") as fout:
    fout.write('\x00' * 1024)

i = 0

# node1
while True:
    with open("node1.dat", "r+") as fout:
        with contextlib.closing(mmap.mmap(fout.fileno(), 1024, access=mmap.ACCESS_WRITE)) as m_write:
            m_write.seek(0)
            i += 1
            s = "node1: How are you?" + str(i)
            s.rjust(1024, '\x00')
            m_write.write(s.encode())
            m_write.flush()
            time.sleep(1) 
  • python-node2
import mmap
import contextlib
import time
import random

i = 0

# node2
while True:
    with open('node1.dat', 'r') as fin:
        with contextlib.closing(mmap.mmap(fin.fileno(), 0, access=mmap.ACCESS_READ)) as m:
            s = m.read(1024)
            s = s.decode()
            print(s)
            i += 1
            print("node2: I'm fine, thx!" + str(i))
            time.sleep(1)

除了mmap可以处理共享内存,multiprocessing库同样可以处理,同时还有队列、管道等其他通讯方式,只是只找到python进程之间通讯的,没有跨语言的实现,参考一文读懂Python进程间通信的几种方式

4. ros/ros2通信

5. 工程代码

repo

simple_python_cpp_communication

cpp编译

cd cpp/test/build
cmake .. && make -j4

管道通信

  1. python-cpp
# 终端1
python python/fifo_node1_test.py
# 终端2
cpp/test/build/fifo_node2_test
  1. cpp-cpp
# 终端1
cpp/test/build/fifo_node1_test
# 终端2
cpp/test/build/fifo_node2_test
  1. python-python
# 终端1
python python/fifo_node1_test.py
# 终端2
python python/fifo_node2_test.py

socket通信

  1. python-cpp
# 终端1
python python/socket_server.py
# 终端2
cpp/test/build/socket_clien_test
  1. cpp-cpp
# 终端1
cpp/test/build/socket_server_test
# 终端2
cpp/test/build/socket_clien_test
  1. python-python
# 终端1
python python/socket_server.py
# 终端2
python python/socket_client.py

共享内存

  1. python-python(未同步)
# 终端1
python python/mmap_node1.py
# 终端2
python python/mmap_node2.py
  1. cpp-cpp(同步)
# 终端1
cpp/test/build/sm_server
# 终端2
cpp/test/build/sm_client

参考

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到