c python通信protobuf_基于ProtoBuf-3 实现的C++(客户端)与Python(服务端)通信

一、协议编写

1、Msg.proto,每一行的意思都写得很清楚了。Msg可以理解成一个顶层消息容器,里面可以放置登录、识别等消息。

syntax = "proto3"; // 指定ProtoBuf得版本,省略本行默认为2版本,如果使用3版本这句不可以省略

option optimize_for = LITE_RUNTIME; // 使用清凉版,没有反射等高级功能

package VxIVideo; // 包名,其实proto.exe编译后变成了命名空间

import "Login.proto"; // 导入依赖得包

import "Logout.proto"; // 导入依赖得包

import "Identify.proto"; // 导入依赖得包

message Msg // 定义一个消息,这里是朱消息

{

enum eMsgType // 枚举消息类型

{

MSG_TYPE_LOGIN_REQ = 0x0000;

MSG_TYPE_LOGIN_RSP = 0x0001;

MSG_TYPE_LOGOUT_NOTIFY = 0x0002;

MSG_TYPE_IDENTIFY_REQ = 0x0003;

MSG_TYPE_IDENTIFY_RSP = 0x0004;

}

eMsgType msg_type = 1; // 消息类型

LoginReq login_req = 2; // 登录请求

LoginRsp login_rsp = 3; // 登录响应

LogoutNotify logout_notify = 4; // 登出通知

IdentifyReq identify_req = 5; // 识别请求

IdentifyRsp identify_rsp = 6; // 识别响应

}

2、Login.proto,登录协议

syntax = "proto3";

option optimize_for = LITE_RUNTIME;

package AI;

message LoginReq

{

string username = 1;

string password = 2;

}

message LoginRsp

{

enum eLoginRet

{

LOGIN_SUCCESS = 0x0000;

LOGIN_ACCOUNT_NULL = 0x0001;

LOGIN_ACCOUNT_LOCK = 0x0002;

LOGIN_PASSWORD_ERROR = 0x0003;

LOGIN_LOGIN_ERROR = 0x0004;

}

eLoginRet ret = 1;

}

3、Logout.proto,登出协议

syntax = "proto3";

option optimize_for = LITE_RUNTIME;

package AI;

message LogoutNotify

{

}

4、Identify.proto,识别协议

syntax = "proto3";

option optimize_for = LITE_RUNTIME;

package AI;

message IdentifyReq

{

message Image

{

int32 format = 1;

uint32 size = 2;

bytes data = 3;

}

int32 camera_id = 1;

repeated Image img = 2;

}

message IdentifyRsp

{

enum eIdentifyRet

{

IDENTIFY_SUCCESS = 0x0000;

IDENTIFY_FAILED = 0x0001;

}

eIdentifyRet ret = 1;

string result = 2;

}

二、生成C++和Python协议文件

// $SRC_DIR: .proto 所在的源目录

// --cpp_out: 生成 c++ 代码

// $DST_DIR: 生成代码的目标目录

// xxx.proto: 要针对哪个 proto 文件生成接口代码

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/xxx.proto

protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/xxx.proto

生产如下文件,将生成的文件添加到工程中即可。

三、编码实现

1、服务端

# !usr/bin/python

# -*- coding:utf-8 -*-

""" A tcp dientify server"""

__author__ = "huzhenhong@2019-12-16"

import socketserver

import time

import struct

import Msg_pb2

class ClientMsgHandle(socketserver.BaseRequestHandler):

def handle(self):

while True: # 每个新的连接都有自己的消息循环

try:

self.data = self.request.recv(1024)

data_len = int(self.data[:4].decode('ascii'), 16)

print(data_len)

self.data = self.data[4:]

if data_len + 4 > 1024:

# 还需要读取的长度

need_more_data_len = data_len - (1024 - 4)

read_sum = int(need_more_data_len / 1024)

surplus_len = int(need_more_data_len % 1024)

for _ in range(read_sum):

self.data += self.request.recv(1024)

self.data += self.request.recv(surplus_len)

msg = Msg_pb2.Msg()

msg.ParseFromString(self.data)

if msg.eMsgType.MSG_TYPE_LOGIN_REQ == msg.msg_type:

self.handle_login_req(msg.login_req)

elif msg.eMsgType.MSG_TYPE_LOGOUT_NOTIFY == msg.msg_type:

self.handle_logout_notify()

break # 退出消息循环

elif msg.eMsgType.MSG_TYPE_IDENTIFY_REQ == msg.msg_type:

self.handle_identify_req(msg.identify_req)

else:

print('error message type!')

except Exception as e:

print(self.client_address,"连接已断开")

identify_server.shutdown()

break

finally:

print('finish handle')

time.sleep(0.1)

self.request.close()

def handle_login_req(self, login_req):

"""处理登录请求:param login_req::return:"""

print('login req, username: {}, password: {}'.format(login_req.username, login_req.password))

login_rsp_msg = Msg_pb2.Msg()

login_rsp_msg.msg_type = login_rsp_msg.eMsgType.MSG_TYPE_LOGIN_RSP

login_rsp_msg.login_rsp.ret = login_rsp_msg.login_rsp.eLoginRet.LOGIN_SUCCESS

self.send_msg(login_rsp_msg)

def handle_identify_req(self, identify_req):

"""处理识别请求:param identify_req::return:"""

print('identify req, camera_id: {}'.format(identify_req.camera_id))

imgs = identify_req.img

[print('image size: {}, format: {}'.format(im.size, im.format)) for im in imgs]

img = imgs[0]

f = open("out.png", 'wb') # 二进制写模式

f.write(bytes(img.data)) # 二进制写

identify_rsp_msg = Msg_pb2.Msg()

identify_rsp_msg.msg_type = identify_rsp_msg.eMsgType.MSG_TYPE_IDENTIFY_RSP

identify_rsp_msg.identify_rsp.ret = identify_rsp_msg.identify_rsp.eIdentifyRet.IDENTIFY_SUCCESS

identify_rsp_msg.identify_rsp.result = 'normal'

self.send_msg(identify_rsp_msg)

def handle_logout_notify(self):

"""处理登出:return:"""

print(self.client_address, "主动断开")

identify_server.shutdown()

def send_msg(self, msg):

"""发送请求响应:param rsp_bytes::return:"""

rsp_bytes = msg.SerializeToString()

# 数据长度unsinged int 转 bytes

head = struct.pack(">I", len(rsp_bytes))

self.request.sendall(head + rsp_bytes)

HOST, PORT = "172.30.1.173", 8888

# identify_server = socketserver.ThreadingTCPServer((HOST, PORT), ClientMsgHandle)

identify_server = socketserver.ForkingTCPServer((HOST, PORT), ClientMsgHandle)

identify_server.serve_forever()

2、客户端

// 头文件#pragma once#include #include #include #include "Msg.pb.h"

class CIdentifyClient

{

public:

static CIdentifyClient * Instance();

bool Initialize(const std::string & ip, const unsigned int port);

void Uninitialize();

void Login(const std::string & username, const std::string & password);

void Logout();

void Identify();

private:

void SendMsg(const AI::Msg & msg);

void OnLoginResponse(const AI::LoginRsp& loginRsp);

void OnIdentifyResponse(const AI::IdentifyRsp& identifyRsp);

void HandelMsg();

unsigned int GetDataLen(const std::string & head);

private:

CIdentifyClient()

: m_pThread(nullptr)

{

}

~CIdentifyClient(){}

CIdentifyClient(const CIdentifyClient &) = delete;

CIdentifyClient & operator=(const CIdentifyClient &) = delete;

private:

SOCKET m_socket;

std::thread * m_pThread;

};

// cpp文件#include "IdentifyClient.h"#pragma comment(lib,"ws2_32.lib")#pragma warning(disable:4996)#include #include #include #include "spdlog/spdlog.h"

CIdentifyClient * CIdentifyClient::Instance()

{

static CIdentifyClient instance;

return &instance;

}

bool CIdentifyClient::Initialize(const std::string & ip, const unsigned int port)

{

WORD wVersionRequested;

WSADATA wsaData;

wVersionRequested = MAKEWORD(1, 1);

int err = WSAStartup(wVersionRequested, &wsaData);

if (err != 0)

{

spdlog::info("WSAStartup failed.");

return false;

}

if (LOBYTE(wsaData.wVersion) != 1 ||

HIBYTE(wsaData.wVersion) != 1)

{

spdlog::info("wsaData.wVersion failed.");

WSACleanup();

return false;

}

m_socket = socket(AF_INET, SOCK_STREAM, 0);

SOCKADDR_IN addrSrv;

addrSrv.sin_addr.S_un.S_addr = inet_addr(ip.data());

addrSrv.sin_family = AF_INET;

addrSrv.sin_port = htons(port);

int ret = connect(m_socket, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

if (SOCKET_ERROR == ret)

{

spdlog::info("connect failed.");

return false;

}

std::thread networkThread(&CIdentifyClient::HandelMsg, this);

networkThread.detach();

return true;

}

void CIdentifyClient::Uninitialize()

{

closesocket(m_socket);

WSACleanup();

}

void CIdentifyClient::Login(const std::string & username, const std::string & password)

{

AI::LoginReq * pLoginReq = new AI::LoginReq;

pLoginReq->set_username(username);

pLoginReq->set_password(password);

AI::Msg msg;

msg.set_msg_type(AI::Msg::MSG_TYPE_LOGIN_REQ);

msg.set_allocated_login_req(pLoginReq);

SendMsg(msg);

}

void CIdentifyClient::Logout()

{

AI::LogoutNotify * pLogoutNotify = new AI::LogoutNotify;

AI::Msg msg;

msg.set_msg_type(AI::Msg::MSG_TYPE_LOGOUT_NOTIFY);

msg.set_allocated_logout_notify(pLogoutNotify);

SendMsg(msg);

}

void CIdentifyClient::Identify()

{

// 加载图片 std::ifstream infile("D:\\MyStudy\\protobuf\\ProtolbufTest\\x64\\Release\\in.png", std::ios::binary);

if (!infile)

{

spdlog::info("open img failed.");

return;

}

infile.seekg(0, std::ios::end);

int fileSize = infile.tellg();

infile.seekg(std::ios::beg);

char * pBmp = (char *)malloc(fileSize);

infile.read(pBmp, fileSize);

infile.close();

// 设置图片 AI::IdentifyReq * pIdentifyReq = new AI::IdentifyReq;

AI::IdentifyReq::Image * pImg = pIdentifyReq->add_img();

pImg->set_data(pBmp, fileSize);

pImg->set_format(1);

pImg->set_size(fileSize);

// 设置消息 AI::Msg msg;

msg.set_msg_type(AI::Msg::MSG_TYPE_IDENTIFY_REQ);

msg.set_allocated_identify_req(pIdentifyReq);

SendMsg(msg);

}

void CIdentifyClient::SendMsg(const AI::Msg & msg)

{

auto data = msg.SerializeAsString();

char head[4];

sprintf(head, "%4x", data.size());

std::string sendData = head + data;

send(m_socket, sendData.data(), sendData.size(), 0);

}

void CIdentifyClient::OnLoginResponse(const AI::LoginRsp & loginRsp)

{

auto ret = loginRsp.ret();

std::cout << "login ret : " << ret << std::endl;

Identify();

}

void CIdentifyClient::OnIdentifyResponse(const AI::IdentifyRsp & identifyRsp)

{

auto ret = identifyRsp.ret();

auto result = identifyRsp.result();

std::cout << "identify ret : " << ret << " result : " << result << std::endl;

}

void CIdentifyClient::HandelMsg()

{

while (true)

{

std::string recvData;

char buf[1024];

int ret = recv(m_socket, buf, 1024, 0);

if (ret <= 0)

{

Sleep(100);

continue;

}

int haveReadLen = ret - 4;

std::string head;

head.resize(4);

head[0] = buf[0];

head[1] = buf[1];

head[2] = buf[2];

head[3] = buf[3];

//int i = std::stoi(head, 0, 16);

int dataLen = GetDataLen(head);

while (haveReadLen != dataLen)

{

ret = recv(m_socket, buf, 1024, 0);

if (SOCKET_ERROR == ret)

{

continue;

}

haveReadLen += ret;

}

std::string data;

data.resize(dataLen);

for (int i = 0; i < dataLen; ++i)

{

data[i] = buf[i + 4];

}

AI::Msg msg;

auto result = msg.ParseFromString(data);

if (!result)

{

// return -1; }

auto type = msg.msg_type();

switch (msg.msg_type())

{

case AI::Msg::MSG_TYPE_LOGIN_RSP:

{

OnLoginResponse(msg.login_rsp());

break;

}

case AI::Msg::MSG_TYPE_IDENTIFY_RSP:

{

OnIdentifyResponse(msg.identify_rsp());

break;

}

default:

break;

}

Sleep(100);

}

}

unsigned int CIdentifyClient::GetDataLen(const std::string & head)

{

unsigned int sum = 0;

for (int i = 0; i < head.size(); ++i)

{

unsigned int tmp = head[i];

tmp << (head.size() - 1 - i);

sum += tmp;

}

return sum;

}

3、主程序

#include "spdlog/spdlog.h"#include "IdentifyClient.h"

int main()

{

std::string ip = "172.30.1.173";

unsigned int port = 8888;

if (!CIdentifyClient::Instance()->Initialize(ip, port))

{

spdlog::error("Network connect failed!");

return -1;

}

std::string username = "admin";

std::string password = "1234";

CIdentifyClient::Instance()->Login(username, password);

getchar();

return 0;

}

四、总结

1、每个消息类在使用完成后都会自动析构,比如

VxMessage::~VxMessage() {

// @@protoc_insertion_point(destructor:VxIVideo.VxMessage) SharedDtor();

}

void VxMessage::SharedDtor() {

#ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER if (this != &default_instance()) {

#else if (this != default_instance_) {

#endif delete login_req_;

delete login_rsp_;

delete logout_notify_;

delete identify_req_;

delete identify_rsp_;

}

}

亦即会调用delete删除每一个字段,然如果字段定义在栈上,此时就崩溃了。有两种处理方式,一种是上述采用的,每个字段都在堆上new出来,另外还有一种方式是SendMsg(msg)之后马上调用VxMsg.release_identify_rsp()来释放掉该字段,这样在析构的时候identify_rsp_字段就是NULL,可能是protobuf重载过delete了,但是又没找到重载的地方,此时delete就不会出错。(有清楚的童鞋请不吝赐教)

2、对消息的解析就算成功也可能返回false,网上查证说是protobuf3的一个bug

3、char ch = '\xa';直接强转就可以得到十进制数值。

4、数据发送时在源数据之上添加固定长度为4的消息头,以确保可能拿到完成的有效数据进行解析。简单解决TCP粘包问题,对TCP协议有了更深的了解。

5、python socketserver 模块已经封装好了网络和多线程多进程,C++的实现上等待后续添加短线重连、线程池。

你可能感兴趣的:(c)