C++与Python之间跨进程通信(socket实现)_通过socket实现python和c++数据传输-程序员宅基地

技术标签: 服务器相关  python  rpc  多线程  socket  

C++与Python之间跨进程通信(socket实现)

1.引言

之前写过一篇Python调用C++程序的实现方法,这里相反,希望使用Python协助C++完成某些任务。一种解决思路为实现RPC调用,使用C++端(以下称客户端)发送数据,Python端(以下称服务端)处理数据并返回的方法,进一步来说,转换为C++与Python之间通信的问题。

2.实现思路

因为客户端可能希望使用的函数多种多样,这里为了保证灵活,服务端与客户端均写成框架的形式(取名CPhone),在实际使用的过程中,可以方便服务端即时自定义函数,客户端灵活调用。服务端文件结构如下:
在这里插入图片描述
包CPhone为服务器框架,在实际使用的时候只需编写test.py文件中的内容(定义函数)即可。
客户端:
在这里插入图片描述
使用的时候,通过callPyFun()函数调用即可。
为了统一数据格式,在服务端使用numpy的ndarray数据类型,在客户端使用opencv的mat数据类型。

3. 具体代码

(1)Python服务端

CPhone/__init__.py

import numpy as np
import socket
from datetime import datetime
from threading import Thread
import time
import os
from operator import methodcaller


class CPhone:
    def __init__(self):
        self.g_conn_pool = []
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.thread = Thread(target=self._accept_client) #主线程
        self.thread.setDaemon(True)

    def run(self,ip="localhost",port=8888,max_n=0):
        self.server.bind((ip, port))
        self.server.listen(max_n)
        self.thread.start()

        log = """--------------------------\n--------------------------
%s\t\n(ip=%s,port=%d)\n\tstart listening...
"""% (str(datetime.now()), ip, port)
        if os.path.exists('./log.txt'):
            with open('./log.txt','a',encoding='utf-8') as fo:
                fo.write(log)
        else:
            with open('./log.txt','w',encoding='utf-8') as fo:
                fo.write(log)
        print(log)
        while True:
            cmd = input("""--------------------------
    输入1:查看当前在线人数
    输入0:关闭服务器
--------------------------
""")
            if cmd == '1':
                print("--------------------------")
                print("当前在线人数:\n", len(self.g_conn_pool))
            elif cmd == '0':
                exit()

    def _data_handle(self,client,address): #此函数主要用于解析数据,调用函数处理,并将结构序列化为字符串
        func_name,mat = self._recvall(client)
        #cv2.imwrite('xx.jpg', mat)

        return_data = methodcaller(func_name,mat)(self)

        shape_str = [str(i) for i in return_data.shape]
        shape_str = ','.join(shape_str)
        data_str = str(return_data.reshape(-1).tolist()).replace(' ', '').replace('[', ',').replace(']', ',')
        shape_str = ',%s,%d,' % (shape_str, len(data_str))
        self._sendall(client, shape_str, data_str)
        time.sleep(2)
        client.close()
        self.g_conn_pool.remove(client)
        log = ('%s\t用户(ip:%s port:%d)\t已下线\n'% (str(datetime.now()),address[0], address[1]))
        with open('./log.txt', 'a',encoding='utf-8') as fo:
            fo.write(log)
        print(log)
'''
为了解决C++栈溢出的问题,在发送和接受数据的时候均循环传输。
发送数据时先发送头信息,主要储存数据的shape,调用的函数名等。
'''

    def _recvall(self,sock):
        header = sock.recv(20)
        header = str(header).split('\\',1)[0].replace("b'",'')
        func_name,channels,height,width = header.split(',')
        channels,height,width = int(channels),int(height),int(width)
        count = channels*height*width
        buf = b''
        while count:
            newbuf = sock.recv(count)
            if not newbuf: return None
            buf += newbuf
            count -= len(newbuf)
        data = np.frombuffer(buf, dtype='uint8')
        mat = data.reshape(height, width, channels)
        return func_name,mat

    def _sendall(self,sock,header_str,data_str):
        sock.send(bytes(header_str, encoding="ascii"))
        data_bt = bytes(data_str, encoding="ascii")
        total = len(data_str)
        sended = 0
        while sended<total:
            send = min(total-sended,128*128*3)
            sock.send(data_bt[sended:sended+send])
            sended += send

    def _accept_client(self): #此函数用于接收客户端的链接请求,并为其开启一个线程
        while True:
            client, address = self.server.accept()  # 阻塞,等待客户端连接
            # 加入连接池
            self.g_conn_pool.append(client)
            log = ('%s\t用户(ip:%s port:%d)\t已上线\n' % (str(datetime.now()), address[0], address[1]))
            with open('./log.txt','a',encoding='utf-8') as fo:
                fo.write(log)
            print(log)
            # 给每个客户端创建一个独立的线程进行管理
            thread = Thread(target=self._data_handle, args=(client,address))
            # 设置成守护线程
            thread.setDaemon(True)
            thread.start()


实际使用过程中,需进行如下操作
test.py

from CPhone import CPhone
import numpy as np
import torchvision.transforms as transforms


class MyCPhone(CPhone): #继承CPhone
    def normalize(self,mat): #这里定义一个归一化函数测试
        transform = transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize(
                mean=np.array([0.5, 0.5, 0.5]),
                std=np.array([0.5, 0.5, 0.5])),
        ])
        remat = transform(mat).numpy()
        return remat

    def mean(self,mat): #这里定义一个求均值函数测试
        remat = np.mean(mat)
        remat = np.array([remat])
        return remat


if __name__ == '__main__':
    test = MyCPhone()
    test.run() #开启服务

(2)C++客户端

client.cpp

#include <stdio.h>  
#include <Winsock2.h>  
#pragma comment(lib,"ws2_32.lib")
#pragma warning(disable:4996)
#include <iostream>
#include <opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <cmath>
#include <vector>
#define BuffSize 1024
#define MaxReceive 256*256*3

class CPhone
{
    
private:
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	SOCKET sockClient;
	SOCKADDR_IN addrSrv;
	int sec;
public:
	CPhone(const char*,int);
	cv::Mat callPyFun(const char*, cv::Mat);
	cv::Mat createMatFromImage(const char*);
};

CPhone::CPhone(const char* ip="127.0.0.1",int port=8888)
{
    
	this->wVersionRequested = MAKEWORD(1, 1);

	this->err = WSAStartup(this->wVersionRequested, &(this->wsaData));
	if (this->err != 0) {
    
		throw "error!";
	}

	if (LOBYTE(this->wsaData.wVersion) != 1 ||
		HIBYTE(this->wsaData.wVersion) != 1) {
    
		WSACleanup();
		throw "error!";
	}
	this->sockClient = socket(AF_INET, SOCK_STREAM, 0);
	this->addrSrv.sin_addr.S_un.S_addr = inet_addr(ip);
	this->addrSrv.sin_family = AF_INET;
	this->addrSrv.sin_port = htons(port);
	this->sec = connect(this->sockClient, (SOCKADDR*)&this->addrSrv, sizeof(SOCKADDR));
	if (SOCKET_ERROR == this->sec)
	{
    
		throw "error!";
	}
}


cv::Mat CPhone::callPyFun(const char* funName, cv::Mat mat)
{
    
	char* pos = (char*)mat.data;
	int colByte = mat.cols * mat.channels() * sizeof(uchar);
	//cout << mat.size[0];
	char header[30];
	//memcpy(header, &data[], 15);  //buf为储存复制内容的目标数组,sed 为字节数
	sprintf(header, "%s,%d,%d,%d",funName,mat.channels(),mat.size[0],mat.size[1]);
	send(this->sockClient, header, 30, 0);
	for (int i = 0; i < mat.rows; i++)
	{
    
		char* data = (char*)mat.ptr<uchar>(i); //第i行首地址
		int sedNum = 0;
		char buf[BuffSize] = {
     0 };

		//按行拆分,发送数据
		while (sedNum < colByte)
		{
    
			int sed = BuffSize<(colByte - sedNum)?BuffSize: (colByte - sedNum);
			memcpy(buf, &data[sedNum], sed);  //buf为储存复制内容的目标数组,sed 为字节数
			int SendSize = send(this->sockClient, buf, sed, 0);
			if (SOCKET_ERROR == SendSize)
			{
    
				cv::Mat mat;
				return  mat;
			}
			/*else
			cout << SendSize << endl;*/
			sedNum += SendSize;
		}
	}
	char shape_str[40]; //收到的shape的字符串
	
	int ns1 = recv(this->sockClient, shape_str,40, 0); //收到的字符串的长度
	shape_str[ns1] = '\0';
	int shape[6] = {
    0,0,0,0,0,0}; //shape的值
	char temp_num[40];  //当前储存的数字字符
	int isnum = 0; //当前储存了几个字符
	for (int i = 0; i < ns1; i++)
	{
    
		if (shape_str[i]==',')
		{
    
			if (isnum) // 已经储存了字符
			{
    
				temp_num[isnum] = '\0';
				shape[shape[0]+1] = atoi(temp_num);
				shape[0]++;
				isnum = 0;
			}
			else //开始的时候没有储存字符
			{
    
				continue;
			}
		}
		else //数字字符
		{
    
			temp_num[isnum] = shape_str[i];
			isnum++;
		}
	}
	int n_num = 1;
	for (int i = 0; i < shape[0]-1; i++)
	{
    
		n_num *= shape[i + 1];
	}
	int str_len = shape[shape[0]];
	std::vector<double> vet;
	isnum = 0;  //已经储存的字符
	int ns2;
	while (str_len > 0) //循环接收
	{
    
		char* recvBuf = new char[MaxReceive];//收到的数据
		ns2 = recv(this->sockClient, recvBuf, MaxReceive, 0); //返回字符串的长度
		str_len -= ns2;
		recvBuf[ns2] = '\0';
		for (int i = 0; i < ns2; i++)
		{
    
			if (recvBuf[i] == ',') //如果遇到','
			{
    
				if (isnum) //如果已经储存了字符
				{
    
					temp_num[isnum] = '\0';
					vet.push_back(atof(temp_num));
					isnum = 0;
				}
				else //此时为开始的时候
				{
    
					continue;
				}
			}
			else
			{
    
				temp_num[isnum] = recvBuf[i];
				isnum++;
			}
		}
	}
	cv::Mat return_mat = cv::Mat(vet,true);
	switch (shape[0]-1)
	{
    
	case 1:
		return return_mat.reshape(shape[1]).clone();
		break;
	case 2:
		return return_mat.reshape(shape[1],shape[2]).clone();
		break;
	case 3:
		return return_mat.reshape(shape[3],(shape[1], shape[2])).clone();
		break;
	default:
		cv::Mat mat;
		return  mat;
		break;
	}
}

cv::Mat CPhone::createMatFromImage(const char *path)
{
    
	return cv::imread(path, cv::IMREAD_UNCHANGED);
}



int main()
{
    
	CPhone phone;
	cv::Mat img = phone.createMatFromImage("C:\\Users\\Lenovo\\Desktop\\xx.jpg"); //这里读取一张3*640*640的图片
	cv::Mat remat;
	remat = phone.callPyFun("normalize",img).clone(); //参数为调用的函数名,mat数据
	std::cout << remat;
	return 0;
}

使用的时候只需编写main()函数里的代码即可调用。
以下为对两个函数调用的返回结果
mean
在这里插入图片描述

normalize:
在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/CYS_zxcvbnm/article/details/107986385

智能推荐

基于Kepler.gl 和 Google Earth Engine 卫星数据创建拉伸多边形地图-程序员宅基地

文章浏览阅读965次,点赞18次,收藏21次。现在我们有了 2021 年和 2023 年的 NDVI 数据帧,我们需要从 2021 年的值中减去 2023 年的值以捕获 NDVI 的差异。该数据集包括像素级别的植被值,我们将编写一个自定义函数来根据红色和绿色波段的表面反射率计算 NDVI。在我的上一篇文章中,我演示了如何将单个多边形分割/镶嵌为一组大小均匀的六边形。现在我们有了植被损失数据,让我们使用 Kepler.gl 可视化每个六边形的植被损失。将地图保存为 HTML 文件,在浏览器中打开 HTML 以获得更好的视图。现在我们将调用该函数并使用、

Echarts绘制任意数据的正态分布图_echarts正态分布图-程序员宅基地

文章浏览阅读3.3k次,点赞6次,收藏5次。正态分布,又称高斯分布或钟形曲线,是统计学中最为重要和常用的分布之一。_echarts正态分布图

Android中发送短信等普通方法_android bundle.get("pdus");-程序员宅基地

文章浏览阅读217次。首先要在Mainfest.xml中加入所需要的权限:[html] view plain copyprint?uses-permission android:name="android.permission.SEND_SMS"/> uses-permission android:name="android.permission.READ_SMS"/> _android bundle.get("pdus");

2021-07-26 WSL2 的安装和联网_wsl2 联网-程序员宅基地

文章浏览阅读2.6k次。0、说明最近在学习 Data Assimilation Research Testbed (DART) 相关内容,其软件是在 Unix/Linux 操作系统下编译和运行的 ,由于我的电脑是 Windows 10 的,DART 推荐可以使用 Windows Subsystem For Linux (WSL) 来创建一个 Windows 下的 Linux 子系统。以下的内容主要介绍如何安装 WSL2,以及 WSL2 的联网。1、如何在 Windows 10 下安装WSL具体的安装流程可以在 microso_wsl2 联网

DATABASE_LINK 数据库连接_添加 database link重复的数据库链接命-程序员宅基地

文章浏览阅读1k次。DB_LINK 介绍在本机数据库orcl上创建了一个prod_link的publicdblink(使用远程主机的scott用户连接),则用sqlplus连接到本机数据库,执行select * from scott.emp@prod_link即可以将远程数据库上的scott用户下的emp表中的数据获取到。也可以在本地建一个同义词来指向scott.emp@prod_link,这样取值就方便多了..._添加 database link重复的数据库链接命

云-腾讯云-实时音视频:实时音视频(TRTC)-程序员宅基地

文章浏览阅读3.1k次。ylbtech-云-腾讯云-实时音视频:实时音视频(TRTC)支持跨终端、全平台之间互通,从零开始快速搭建实时音视频通信平台1.返回顶部 1、腾讯实时音视频(Tencent Real-Time Communication,TRTC)拥有QQ十几年来在音视频技术上的积累,致力于帮助企业快速搭建低成本、高品质音视频通讯能力的完整解决方案。..._腾讯实时音视频 分享链接

随便推点

用c语言写个日历表_农历库c语言-程序员宅基地

文章浏览阅读534次,点赞10次,收藏8次。编写一个完整的日历表需要处理许多细节,包括公历和农历之间的转换、节气、闰年等。运行程序后,会输出指定年份的日历表。注意,这个程序只是一个简单的示例,还有很多可以改进和扩展的地方,例如添加节气、节日等。_农历库c语言

FL Studio21.1.1.3750中文破解百度网盘下载地址含Crack补丁_fl studio 21 注册机-程序员宅基地

文章浏览阅读1w次,点赞28次,收藏27次。FL Studio21.1.1.3750中文破解版是最优秀、最繁荣的数字音频工作站 (DAW) 之一,日新月异。它是一款录音机和编辑器,可让您不惜一切代价制作精美的音乐作品并保存精彩的活动画廊。为方便用户,FL Studio 21提供三种不同的版本——Fruity 版、Producer 版和签名版。所有这些版本都是独一无二的,同样具有竞争力。用户可以根据自己的需要选择其中任何一种。FL Studio21.1.1.3750中文版可以说是一站式综合音乐制作单位,可以让您录制、作曲、混音和编辑音乐。_fl studio 21 注册机

冯.诺伊曼体系结构的计算机工作原理是,冯 诺依曼型计算机的工作原理是什么...-程序员宅基地

文章浏览阅读1.3k次。冯诺依曼计算机工作原理冯 诺依曼计算机工作原理的核心是 和 程序控制世界上不同型号的计算机,就其工作原理而言,一般都是认为冯 诺依曼提出了什么原理冯 诺依曼原理中,计算机硬件系统由那五大部分组成的 急急急急急急急急急急急急急急急急急急急急急急冯诺依曼结构计算机工作原理的核心冯诺依曼结构和现代计算机结构模型 转载重学计算机组成原理 一 冯 诺依曼体系结构从冯.诺依曼的存储程序工作原理及计算机的组成来..._简述冯诺依曼计算机结构及工作原理

四国军棋引擎开发(2)简单的事件驱动模型下棋-程序员宅基地

文章浏览阅读559次。这次在随机乱下的基础上加上了一些简单的处理,如进营、炸棋、吃子等功能,在和敌方棋子产生碰撞之后会获取敌方棋子大小的一些信息,目前采用的是事件驱动模型,当下完一步棋界面返回结果后会判断是否触发了相关事件,有事件发生则处理相关事件,没有事件发生则仍然是随机下棋。1.事件驱动模型首先定义一个各种事件的枚举变量,目前的事件有工兵吃子,摸暗棋,进营,明确吃子,炸棋。定义如下:enum MoveE..._军棋引擎

STL与泛型编程-第一周笔记-Geekband-程序员宅基地

文章浏览阅读85次。1, 模板观念与函数模板简单模板: template< typename T > T Function( T a, T b) {… }类模板: template struct Object{……….}; 函数模板 template< class T> inline T Function( T a, T b){……} 不可以使用不同型别的..._geekband 讲义

vb.net正则表达式html,VB.Net常用的正则表达式(实例)-程序员宅基地

文章浏览阅读158次。"^\d+$"  //非负整数(正整数 + 0)"^[0-9]*[1-9][0-9]*$"  //正整数"^((-\d+)|(0+))$"  //非正整数(负整数 + 0)"^-[0-9]*[1-9][0-9]*$"  //负整数"^-?\d+$"    //整数"^\d+(\.\d+)?$"  //非负浮点数(正浮点数 + 0)"^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0..._vb.net 正则表达式 取html中的herf