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;
}