c++以及golang基于protobuf进行简单通讯【c++与golang】【项目研究】


前言

当我们需要在不同的编程语言之间进行通讯时,通常会遇到数据格式不兼容的问题。在这种情况下,使用数据序列化工具就成为了一种非常有效的解决方案。Google 推出的 Protocol Buffers(简称 protobuf)就是一种非常常用的数据序列化工具,它可以帮助我们快速实现跨语言、跨平台的数据传输等应用。
C++ 和 Go 是两种常用的编程语言,本文将介绍如何利用 protobuf 实现 C++ 和 Go 语言之间的通讯。为微服务框架打基础。

一、C++和Go语言都可以对protobuf进行操作

Protocol Buffers 是一种轻巧高效的数据序列化工具,通过将结构化数据编码为二进制格式的消息,从而实现方便地进行数据交换和存储,并能够在不同的语言和平台间进行数据互通。因此利用 protobuf 实现 C++ 和 Go 语言之间的通讯是完全可行的,只要共同采用相同的消息格式定义,并通过相应语言的protobuf库进行序列化和反序列化

与其他序列化方式相比,protobuf 有更高的性能和更小的数据体积,因此广泛应用于 Google 内部以及众多开源项目中。
除了基础的序列化功能,Protobuf 还提供了其他高级特性,如扩展、注释、默认值、嵌套类型、枚举等,使其更加灵活、易用。

总之,protobuf 是一种优秀的数据序列化工具,可帮助开发者快速实现跨语言、跨平台的数据传输等应用。

二、protobuf编写与生成

1、protobuf格式

首先是声明protobuf的版本,以及代码所在的包(对于C++来说是namespace),如果需要生成服务类以及rpc方法描述就需要配置cc_generic_services。

syntax = "proto3"; // 声明了protobuf的版本

package fixbug; // 声明了代码所在的包(对于C++来说是namespace)

// 定义下面的选项,表示生成service服务类和rpc方法描述,
option cc_generic_services = true;

定义数据,可以定义嵌套类型,枚举以及数组等等类型

message ResultCode
{
    int32 errcode = 1;
    bytes errmsg = 2;
}
// 数据   列表   映射表
// 定义登录请求消息类型  name   pwd
message LoginRequest
{
    bytes name = 1;
    bytes pwd = 2;
}
// 定义登录响应消息类型
message LoginResponse
{
    ResultCode result = 1;
    bool success = 2;
}

message GetFriendListsRequest
{
    uint32 userid = 1;
}
message User
{
    bytes name = 1;
    uint32 age = 2;
    // 枚举
    enum Sex
    {
        MAN = 0;
        WOMAN = 1;
    }
    Sex sex = 3;
}
message GetFriendListsResponse
{
    ResultCode result = 1;
    repeated User friend_list = 2;  // 定义了一个列表类型
}

定义描述rpc方法的类型 :

service UserServiceRpc
{
    rpc Login(LoginRequest) returns(LoginResponse);
    rpc GetFriendLists(GetFriendListsRequest) returns(GetFriendListsResponse);
}

2、protobuf的编写

syntax = "proto3";

option go_package="./;userpb";

package userpb; // 声明了代码所在的包(对于C++来说是namespace)

// 定义下面的选项,表示生成service服务类和rpc方法描述,默认不生成
option cc_generic_services = true;

message ResultCode
{
    int32 errcode = 1;
    string errmsg = 2;
}
// 数据   列表   映射表
// 定义登录请求消息类型  name   pwd
message LoginRequest
{
    string name = 1;
    string pwd = 2;
}
// 定义登录响应消息类型
message LoginResponse
{
    ResultCode result = 1;
    bool success = 2;
}

message GetFriendListsRequest
{
    uint32 userid = 1;
}
message User
{
    string name = 1;
    uint32 age = 2;
    // 枚举
    enum Sex
    {
        MAN = 0;
        WOMAN = 1;
    }
    Sex sex = 3;
}
message GetFriendListsResponse
{
    ResultCode result = 1;
    repeated User friend_list = 2;  // 定义了一个列表类型
}

// 在protobuf里面怎么定义描述rpc方法的类型 - service
service UserServiceRpc
{
    rpc Login(LoginRequest) returns(LoginResponse);
    rpc GetFriendLists(GetFriendListsRequest) returns(GetFriendListsResponse);
}

有一个区别c++是可以去掉这一行的

option go_package="./;userpb";

2、两种语言的生成

生成对应的c++文件命令

protoc user.proto --cpp_out=.

生成对应的go文件命令

protoc --go_out=./ ./user.proto

三、c++与go语言操作protobuf不同点

vector 与 repeated bytes
repeated bytes 与go语言汽配的转换

四、c++与go语言基于protobuf进行网络通信

两边通信利用TCP进行连接,之前有分别对go以及c++的网络通信进行总结。
c++与go的TCP网络编程总结地址如下:
c++与go的TCP网络编程总结

1、go语言编写服务提供者

go 如果要使用protobuf先要下载对应的插件
下载go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
确保下面两个命令运行成功:

$ protoc --version
libprotoc 3.19.3

$ protoc-gen-go --version
protoc-gen-go v1.27.1

文件导入"github.com/golang/protobuf/proto"
服务提供者实现的简单逻辑是

// 业务处理函数
func Handler(conn net.Conn)  {
	fmt.Println("gett !!!",)
	buf:=make([]byte,4096)
	for{
		// n不停的监控 客户端发来的信息   如果发来的信息 n==0 那么就表示这个客户端下线了
		// n 传过来的是长度
		n,err:=conn.Read(buf)
		if n==0{
			break
		}
		if err!=nil&&err!=io.EOF{
				fmt.Println("err",err)
		}
		msg:=string(buf[:n])
		fmt.Printf("<<<<<== server get client message:%s \n",msg)


		loginRequest := &userpb.LoginRequest{}
		loginResponse := &userpb.LoginResponse{}
		proto.Unmarshal(buf[:n], loginRequest)
		LoginLogic(loginRequest,loginResponse)
		// 序列化成二进制
		responsemsg, _ := proto.Marshal(loginResponse)
		fmt.Printf("responsemsg: %v\n", responsemsg)

		fmt.Printf("=>>>>>> server sent client message:%s \n",responsemsg)
		conn.Write([]byte(responsemsg))
	}
	conn.Close()
}

其中简单的逻辑函数如下:

func LoginLogic(res *userpb.LoginRequest,req *userpb.LoginResponse){
	fmt.Printf("//rpc LoginLogic//\n")
	fmt.Printf("res.GetName(): %s\n", res.GetName())
	fmt.Printf("res.GetPwd(): %s\n", res.GetPwd())
	// req := &userpb.LoginResponse{
	// 	result: ResultCode{
	// 		Rrrcode:1,
	// 		Errmsg :"no",
	// 	},
	// 	Success: true,
	// }
	req.Success=true
	req.Result=&userpb.ResultCode{
		 Errcode : 123,
		 Errmsg : "yes",
	}

}

2、c++服务调用者

生成之后需要导入库 以及链接

#include "user.pb.h"

调用 res.SerializeToString(&send_str) 方法将 res 对象序列化成二进制格式的字符串,并将结果存储在变量 send_str 中。这里使用的是 Protocol Buffers 序列化方法。,然后发送,发送函数已经在之前的网络编程总结中提前写了。

  LoginRequest res;
   res.set_name("hzl");
   res.set_pwd("123");

     // 序列化
   string send_str;
   res.SerializeToString(&send_str);
   // cout << "" << endl;
   // if (rsp.SerializeToString(&send_str)) {
   //    cout << send_str.c_str() << endl;
   // }
   // 3. 通信
   client.SendMessage(fd, send_str);

接收并且解析成为对应的结构体函数如下:调用 read(fd, recvBuf, sizeof(recvBuf)) 方法读取从服务器端返回的数据,如果出错则直接退出,否则根据读取的数据进行反序列化并输出反序列化后的结果。

在这里,使用的是 LoginResponse 对象对接收的数据进行反序列化

void* ListenFunc(void* arg) {
   char recvBuf[1024];
   int fd = *(int*)arg;
   while (1) {
      int len = read(fd, recvBuf, sizeof(recvBuf));
      if (len == -1) {
         exit(-1);
      } else if (len > 0) {
         // LOG_INFO("<<<<<<== client get data:%s", recvBuf);

         LoginResponse rsp;
         // 获取反序列化的
         if (rsp.ParseFromString(recvBuf)) {
            const ResultCode& result = rsp.result();  // 获取 ResultCode 对象
            int32_t errcode = result.errcode();  // 获取 errcode 字段值
            string errmsg = result.errmsg();     // 获取 errmsg 字段值
            cout << "errcode:" << errcode << "   errmsg:" << errmsg << endl;
            bool success = rsp.success();
            cout << "success:" << success << endl;
         }

      } else if (len == 0) {
         // 表示服务器端断开连接
         LOG_INFO("server closed..");
         exit(1);
         break;
      }
   }
}

总结

文章总结了如何使用 protobuf 实现 C++ 和 Go 语言之间的通讯,包括了 protobuf 的编写与生成、C++ 和 Go 在操作 protobuf 时的一些不同点,以及基于 protobuf 实现网络通信的示例。同时还介绍了 protobuf 的优点以及常用的高级特性。