Windows下C++基于protobuf库自定义协议通信

6 篇文章 149 订阅 ¥9.90 ¥99.00
本文介绍如何在Windows环境下使用C++结合protobuf库实现自定义通信协议。通过序列化和反序列化操作,配合封包解包机制,确保数据传输的正确性。程序采用阻塞式通信方式进行验证。
摘要由CSDN通过智能技术生成

1、采用protobuf库,发送端进行序列化操作,接收端进行反序列化操作。

2、发送端对序列化后的数据进行封包操作,先发送包头,再发送包体;接收端先接收包头,解析出包体的长度,再接收包体,然后对包体数据进行反序列化操作,解析出原始数据。

3、程序采用简单的阻塞式通信方式,主要为了验证协议的正确性。

4、代码:

(1)CMsgHandler类是自己封装的封包类。

#pragma once
#include "client.pb.h"

struct Protobuf_Msg
{
	int nlen = 0;            //结构体总长度
	int nnamelen = 0;        //protobuf消息体message名称长度
	char *szname = nullptr;        //message 名称
	char *szProtobufdata = nullptr;//message序列化后的数据
};

struct Socket_Msg
{
	int nmsgtype = 0;   //消息类型
	int nbuflen = 0;    //后面数据的长度
	char *szbuf = nullptr;    //真正数据
};

class CMsgHandler
{
public:
	CMsgHandler();
	virtual ~CMsgHandler();

public:
	unsigned int GetMsgType() { return m_uiMsgType; }
	google::protobuf::Message* GetMsg() { return m_pMessage; }
	google::protobuf::Message* create_message(const char* typeName);
	bool packetMsg(google::protobuf::Message* pmsg, int nmsgtype, Socket_Msg &socketmsg); //封包
	bool ReceiveMsg(Socket_Msg &smsg);
	void ReleaseMem();

private:
	bool parilizeRecvMsg(Protobuf_Msg *pmsg);
	google::protobuf::Message* m_pMessage;//消息内容指针
	unsigned int m_uiMsgType;             //消息类型
	Socket_Msg m_sockMsg;
	Protobuf_Msg m_protobufMsg;
};
#include "CMsgHandler.h"
#include <string>
using namespace std;


CMsgHandler::CMsgHandler() {}
CMsgHandler:: ~CMsgHandler() {}

google::protobuf::Message* CMsgHandler::create_message(const char* typeName) {
	google::protobuf::Message* message = NULL;
	const google::protobuf::Descriptor* descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(typeName);

	if (descriptor) {
		
		const google::protobuf::Message* prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
		if (prototype) {
			message = prototype->New();
		}
	}
	return message;

}

bool CMsgHandler::packetMsg(google::protobuf::Message* pmsg, int nmsgtype, Socket_Msg &socketmsg)
{
	if (!pmsg) { return false; }
	if ((nmsgtype < 0) || (nmsgtype > 2000)) { return false; }
	if (!pmsg->IsInitialized()) { return false; }

	Protobuf_Msg promsg; int nlen = 0;
	promsg.nnamelen = pmsg->GetTypeName().size();
	nlen = pmsg->ByteSizeLong();

	promsg.szProtobufdata = new char[nlen];
	promsg.szname = new char[promsg.nnamelen];

	if (!promsg.szProtobufdata || !promsg.szname) { return false; }
	
	pmsg->SerializeToArray(promsg.szProtobufdata, nlen);
	
	memcpy(promsg.szname, pmsg->GetTypeName().c_str(), promsg.nnamelen);
	promsg.nlen = nlen + promsg.nnamelen + 8;

	socketmsg.nmsgtype = nmsgtype;
	socketmsg.nbuflen = promsg.nlen;
	socketmsg.szbuf = new char[socketmsg.nbuflen];
	if (!socketmsg.szbuf) { return false; }

	//先拷贝前8个字节(因为内存空间是连续的),再分别拷贝后面两个指针中的内容
	memcpy(socketmsg.szbuf, &promsg, 8);
	memcpy(socketmsg.szbuf + 8, promsg.szname, promsg.nnamelen);
	memcpy(socketmsg.szbuf + 8 + promsg.nnamelen, promsg.szProtobufdata, nlen);

	if (promsg.szname)
		delete []promsg.szname, promsg.szname = nullptr;
	if (promsg.szProtobufdata)
		delete []promsg.szProtobufdata, promsg.szProtobufdata = nullptr;

	return true;
}

bool CMsgHandler::ReceiveMsg(Socket_Msg &smsg)
{
	//先拷贝前8个字节获取名字长度和总长度
	memcpy((char*)&m_protobufMsg, smsg.szbuf, 8);
	m_protobufMsg.szname = new char[m_protobufMsg.nnamelen];
	if (!m_protobufMsg.szname) { return false; }

	int nprotobuflen = m_protobufMsg.nlen - m_protobufMsg.nnamelen - 8;
	m_protobufMsg.szProtobufdata = new char[nprotobuflen];
	if (!m_protobufMsg.szProtobufdata) { return false; }

	//拷贝名字和protobuf内容
	memcpy(m_protobufMsg.szname, smsg.szbuf + 8, m_protobufMsg.nnamelen);
	memcpy(m_protobufMsg.szProtobufdata, smsg.szbuf + 8 + m_protobufMsg.nnamelen, nprotobuflen);

	//解析
	if (!parilizeRecvMsg(&m_protobufMsg)) { return false; }

	return true;
}

bool CMsgHandler::parilizeRecvMsg(Protobuf_Msg *pmsg)
{
	if (!pmsg || !pmsg->szname || !pmsg->szProtobufdata) { return false; }

	//获取message名字,我这里测试的应该是zzc::Client
	string strname(pmsg->szname, pmsg->nnamelen);
	//通过名字获取message
	if (!(m_pMessage = create_message(strname.c_str()))) { return false; }

	//解析
	if (!m_pMessage->ParseFromArray(pmsg->szProtobufdata, pmsg->nlen - pmsg->nnamelen - 8)) { return false; }

	return true;
}

void CMsgHandler::ReleaseMem()
{
	if (m_protobufMsg.szname) { delete[]m_protobufMsg.szname, m_protobufMsg.szname = nullptr; }
	if (m_protobufMsg.szProtobufdata) { delete[]m_protobufMsg.szProtobufdata, m_protobufMsg.szProtobufdata = nullptr; }
	if (m_pMessage) { delete m_pMessage, m_pMessage = nullptr; }
}

(2)服务器端:

// protobuf_server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <winsock2.h>
#include "client.pb.h"
#include "CMsgHandler.h"
#include <iostream>

using namespace std;

#pragma comment (lib, "ws2_32.lib")


int main()
{
	GOOGLE_PROTOBUF_VERIFY_VERSION;

	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
		return 0;

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
		WSACleanup();
		return 0;
	}

	SOCKET sockListen = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(8000);
	
	if (::bind(sockListen, (SOCKADDR*)&addrSrv, sizeof(addrSrv)) == SOCKET_ERROR) { 
		closesocket(sockListen); 
		printf("Bind Error...\n"); }

	if (listen(sockListen, 5) == SOCKET_ERROR) { 
		closesocket(sockListen); 
		printf("Listen Error ...\n"); }
	
	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);

	printf("Wait a connect ...\n");
	while (1)
	{
		SOCKET sockConn = accept(sockListen, (SOCKADDR*)&addrClient, &len);
		printf("send data...\n");
		
		//客户端连接上来之后,服务器给客户端发送一条消息
		zzc::Client client;
		zzc::ClientInfo *cInfo = client.add_client_info();
		cInfo->set_ip(inet_ntoa(addrClient.sin_addr));
		cInfo->set_id(01);

		string *stremail = cInfo->add_email();
		*stremail = "zhangsan@163.com";

		stremail = cInfo->add_email();
		*stremail = "lisi@163.com";

		stremail = cInfo->add_email();
		*stremail = "wangwu@163.com";

		添加第二个client信息/
		cInfo = client.add_client_info();
		cInfo->set_ip(inet_ntoa(addrClient.sin_addr));
		cInfo->set_id(02);

		stremail = cInfo->add_email();
		*stremail = "dongqi@163.com";

		stremail = cInfo->add_email();
		*stremail = "huangba@163.com";

		stremail = cInfo->add_email();
		*stremail = "shijiu@163.com";

		int ntype = 1001;
		CMsgHandler MsgHandler; Socket_Msg sendsmsg;
		
		if (MsgHandler.packetMsg(&client, ntype, sendsmsg)) {
			//先发送8个字节,包含消息类型、数据长度
			send(sockConn, (char*)&sendsmsg, 8, 0);
			send(sockConn, sendsmsg.szbuf, sendsmsg.nbuflen, 0);
		}

		MsgHandler.ReleaseMem();

		cout << "服务器端发送的数据: " << client.DebugString() << endl;

		closesocket(sockConn);
	}

	// Optional:  Delete all global objects allocated by libprotobuf.
	google::protobuf::ShutdownProtobufLibrary();
}

(3)客户端:

// protobuf_client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
//#include "client.pb.h"
#include <winsock2.h>
#include <string>
#include <iostream>
#include "CMsgHandler.h"

#pragma comment (lib, "ws2_32.lib")
using namespace std;

int main()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return 0;
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion != 2))
	{
		WSACleanup();
		return 0;
	}

	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
	
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(8000);

	connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
	printf("Connect Successful...\n");

	//连接上之后,准备接收服务器发送过来的消息
	Socket_Msg smsg;
	CMsgHandler msgHandler;

	//先接收8个字节
	if (recv(sockClient, (char*)&smsg, 8, 0) != SOCKET_ERROR) { 
		
		smsg.szbuf = new char[smsg.nbuflen];
		if (smsg.szbuf) {
			if (recv(sockClient, smsg.szbuf, smsg.nbuflen, 0) != SOCKET_ERROR) {
				//解析收到的数据
				if (msgHandler.ReceiveMsg(smsg)) {
					打印解析的数据
					google::protobuf::Message *pMsg = msgHandler.GetMsg();
					zzc::Client *pClient = (zzc::Client*)pMsg;

					switch (smsg.nmsgtype)
					{
					case 1001:
						for (size_t i = 0; i < pClient->client_info_size(); i++)
						{
							zzc::ClientInfo cInfo = pClient->client_info(i);

							cout << cInfo.ip() << " : " <<cInfo.id() << endl;

							for (size_t j = 0; j < cInfo.email_size(); j++)
							{
								cout << cInfo.email(j) << endl;
							}

							cout << "==========================================================" <<endl;
						}
					default:
						break;
					}

					std::cout << "===========================================" << std::endl;
					std::cout << pMsg->DebugString() << std::endl;

					//清理工作
					if (smsg.szbuf) { delete[]smsg.szbuf, smsg.szbuf = nullptr; }
					msgHandler.ReleaseMem();
				}
			}
		}
	}	

	// Optional:  Delete all global objects allocated by libprotobuf.
	google::protobuf::ShutdownProtobufLibrary();

	cout << "client leave..." << endl;
}

(4)测试client.proto文件:

syntax = "proto2";
package zzc;

message ClientInfo{
	required string ip = 1;
	required int32 id = 2;
	repeated string email = 3;
}

message Client{
	repeated ClientInfo client_info = 1;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小米的修行之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值