【Golang】网络传输protobuf格式数据

1、背景

最近在项目中用遇到用go和c++通过自定义TLV协议传输数据时,V里面放的是json格式的字节数组,在数据量较大的时候性能就特别的差,这时就考虑用protobuf代替json传输数据。传输数据的协议有很多:tcp、udp、unix套接字等,下文用go给出以tcp协议传输protobuf格式数据的例子。

2、命令安装

【1】protoc安装

下载地址:https://github.com/protocolbuffers/protobuf/releases
去此地址页面直接找到Assets部分根据操作系统和架构下载对应的zip包即可,解压zip文件后将对应bin目录里的protoc命令放的自己的环境变量里面,方便全局使用。

protocprotobuf的编译器,用于将.proto文件编译生成不同编程语言的代码。可以通过如下方式检测protoc是否安装好:

$ protoc --version
libprotoc 3.19.4

【2】protoc-gen-go安装

直接安装到GOPATH下的bin目录:go install google.golang.org/protobuf/cmd/protoc-gen-go@latest,
注意GOPATH下的bin目录要放在环境变量下。

protoc-gen-goprotoc的一个插件,用于生成Go语言代码。可以通过如下方式检测protoc-gen-go是否安装好:

$ protoc-gen-go --version
protoc-gen-go.exe v1.32.0

3、代码示例

【1】定义.proto文件

一个简单的test.proto文件,内容如下:

syntax = "proto3";   //protobuf版本

option go_package = "./test_pb";  //包名

message Msg {
  string A = 1;
  int32 B = 2;
}

【2】生成.go文件

可以在.proto文件所在目录定义一个makefile或脚本,当然也可以直接使用命令生成,这里定义makefile如下:

proto:
	protoc --go_out=. --go_opt=paths=source_relative ./*.proto
clear:
	rm -f ./*.go

执行make命令生成.go文件:

$ make proto
protoc --go_out=. --go_opt=paths=source_relative ./*.proto

命令解释:

–go_out:指定生成的Go文件输出到当前目录。
–go_opt=paths=source_relative:表示生成的Go文件会与.proto文件保持相对路径一致。
./*.proto:指定当前目录下的所有.proto文件。

执行make命令后会在当前目录产生一个名为test.pb.go的文件,内容如下:

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// 	protoc-gen-go v1.32.0
// 	protoc        v3.19.4
// source: test.proto

package test_pb

import (
	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
	reflect "reflect"
	sync "sync"
)

const (
	// Verify that this generated code is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
	// Verify that runtime/protoimpl is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

type Msg struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	A string `protobuf:"bytes,1,opt,name=A,proto3" json:"A,omitempty"`
	B int32  `protobuf:"varint,2,opt,name=B,proto3" json:"B,omitempty"`
}

func (x *Msg) Reset() {
	*x = Msg{}
	if protoimpl.UnsafeEnabled {
		mi := &file_test_proto_msgTypes[0]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *Msg) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*Msg) ProtoMessage() {}

func (x *Msg) ProtoReflect() protoreflect.Message {
	mi := &file_test_proto_msgTypes[0]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use Msg.ProtoReflect.Descriptor instead.
func (*Msg) Descriptor() ([]byte, []int) {
	return file_test_proto_rawDescGZIP(), []int{0}
}

func (x *Msg) GetA() string {
	if x != nil {
		return x.A
	}
	return ""
}

func (x *Msg) GetB() int32 {
	if x != nil {
		return x.B
	}
	return 0
}

var File_test_proto protoreflect.FileDescriptor

var file_test_proto_rawDesc = []byte{
	0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x21, 0x0a, 0x03,
	0x4d, 0x73, 0x67, 0x12, 0x0c, 0x0a, 0x01, 0x41, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01,
	0x41, 0x12, 0x0c, 0x0a, 0x01, 0x42, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x42, 0x42,
	0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
	0x6f, 0x74, 0x6f, 0x33,
}

var (
	file_test_proto_rawDescOnce sync.Once
	file_test_proto_rawDescData = file_test_proto_rawDesc
)

func file_test_proto_rawDescGZIP() []byte {
	file_test_proto_rawDescOnce.Do(func() {
		file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)
	})
	return file_test_proto_rawDescData
}

var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_test_proto_goTypes = []interface{}{
	(*Msg)(nil), // 0: Msg
}
var file_test_proto_depIdxs = []int32{
	0, // [0:0] is the sub-list for method output_type
	0, // [0:0] is the sub-list for method input_type
	0, // [0:0] is the sub-list for extension type_name
	0, // [0:0] is the sub-list for extension extendee
	0, // [0:0] is the sub-list for field type_name
}

func init() { file_test_proto_init() }
func file_test_proto_init() {
	if File_test_proto != nil {
		return
	}
	if !protoimpl.UnsafeEnabled {
		file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*Msg); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
	}
	type x struct{}
	out := protoimpl.TypeBuilder{
		File: protoimpl.DescBuilder{
			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
			RawDescriptor: file_test_proto_rawDesc,
			NumEnums:      0,
			NumMessages:   1,
			NumExtensions: 0,
			NumServices:   0,
		},
		GoTypes:           file_test_proto_goTypes,
		DependencyIndexes: file_test_proto_depIdxs,
		MessageInfos:      file_test_proto_msgTypes,
	}.Build()
	File_test_proto = out.File
	file_test_proto_rawDesc = nil
	file_test_proto_goTypes = nil
	file_test_proto_depIdxs = nil
}

【3】protobuf库下载

go get -u google.golang.org/protobuf/proto

【4】服务端代码

l, err := net.Listen("tcp", ":8888")
	if err != nil {
		logger.Error("listen error", zap.Error(err))
		return
	}
	defer l.Close()

	logger.Info("listen tcp port 8888 success")

	for {
		conn, err := l.Accept()
		if err != nil {
			logger.Error("accept error", zap.Error(err))
			continue
		}

		go func() {
			for {
				buf := make([]byte, 65*1024)
				n, err := conn.Read(buf)
				if err != nil {
					conn.Close()

					logger.Error("conn read error", zap.Error(err))
					break
				}

				//解析读到的数据
				msg := test_pb.Msg{}
				if err = proto.Unmarshal(buf[:n], &msg); err != nil {
					logger.Error("proto unmarshal error", zap.Error(err))
					continue
				}

				logger.Info("receive client msg", zap.String("msg", msg.String()))
			}
		}()
	}

运行服务端代码后控制台输出如下:

[2024-10-11 11:23:14.506] | INFO  | Goroutine:1  | [protobuf_demo/main.go:100]  | listen tcp port 8888 success

服务端以tcp协议监听8888端口。

【5】客户端代码

conn, err := net.Dial("tcp", "127.0.0.1:8888")
	if err != nil {
		logger.Error("dial error", zap.Error(err))
		return
	}
	defer conn.Close()

	msg := &test_pb.Msg{}
	msg.A = "aaa"
	msg.B = 111

	bytes, err := proto.Marshal(msg)
	if err != nil {
		logger.Error("proto marshal error", zap.Error(err))
		return
	}

	if _, err = conn.Write(bytes); err != nil {
		logger.Error("conn write error", zap.Error(err))
		return
	}

	logger.Info("conn write success", zap.String("msg", msg.String()))

运行客户端代码后控制台输出如下:

[2024-10-11 11:34:01.258] | INFO  | Goroutine:1  | [protobuf_demo/main.go:156]  | conn write success | {"msg": "A:\"aaa\" B:111"}

此时服务端收到客户端连接,并且解析接收到的数据并打印:

[2024-10-11 11:23:14.506] | INFO  | Goroutine:1  | [protobuf_demo/main.go:100]  | listen tcp port 8888 success
[2024-10-11 11:34:01.258] | INFO  | Goroutine:18 | [protobuf_demo/main.go:127]  | receive client msg | {"msg": "A:\"aaa\" B:111"} //打印解析出来的数据
[2024-10-11 11:34:01.299] | ERROR | Goroutine:18 | [protobuf_demo/main.go:116]  | conn read error | {"error": "EOF"} //客户端发送完数据程序退出就断开连接了,从已关闭的连接读取就会报错。

4、总结

上文go中操作protobuf的步骤为:定义.proto文件、编译生成.go文件、进行序列化和反序列化,以及通过网络传输数据。protobuf还用于RPC协议中,使用protobuf可以定义RPC方法和消息结构,通过protoc编译后自动生成客户端和服务端代码,大大简化PRC系统的实现。谷歌就提供了高效的RPC框架:gRPC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值