解析协议缓冲区时包含无效的UTF-8数据。如果您打算发送原始字节,请使用“bytes”类型问题

1、解析协议缓冲区时包含无效的UTF-8数据。如果您打算发送原始字节,请使用“bytes”类型问题

contains invalid UTF-8 data when parsing a protocol buffer. Use the ‘bytes’ type if you intend to send raw bytes.

we are tying use a C++ client to send request to the C++ server.
the proto param we used has been defined as:

but we get an error:
contains invalid UTF-8 data when parsing a protocol buffer. Use the ‘bytes’ type if you intend to send raw bytes.

message PbRider {
string speed_param = 28;
};

but we get an error:
contains invalid UTF-8 data when parsing a protocol buffer. Use the ‘bytes’ type if you intend to send raw bytes.

我们将使用C++ grpc客户端向C++服务器发送请求。
我们使用的原型参数被定义为:
message PbRider {
string speed_param = 28;
};
但是我们得到一个错误:解析协议缓冲区时包含无效的UTF-8数据。如果您打算发送原始字节,请使用“bytes”类型。

String fields are only intended for string UTF-8 data, so if you need to send non-UTF-8 data then it is best to use a bytes field instead.

字符串字段仅用于存储UTF-8数据,因此如果您需要发送非UTF-8数据,那么最好使用字节改为字段。

But the data has been changed to type-string on C++ server, why does the C++ client cannot parse this data? Maybe the C++ protobuf should do something to deal with those who contains non-UTF-8 data but defined as string in proto?

但是在C++ server上数据已经改成了type-string,为什么C++客户端不能解析这个数据?也许C++ protobuf应该做些什么来处理那些包含非UTF-8数据但在proto中定义为字符串的数据?

I’m sure at least in C++ we validate that strings are UTF-8 during parsing but not during serialization. So if C++ does something similar then it is possible to serialize protos with non-UTF-8 strings, which won’t be detected later until that proto is parsed again.

If you need to be able to store non-UTF-8 data in that field then you might want to consider just changing it from a string field to a bytes field, which is usually a safe change to make.

我确定至少在C++中,我们在解析过程中验证字符串是UTF-8,而不是在序列化过程中。因此,如果C++做了类似的事情,那么就有可能用非UTF-8字符串来序列化proto,这在稍后再次解析proto之前不会被检测到。

如果您需要能够在该字段中存储非UTF-8数据,那么您可能需要考虑将其从字符串字段更改为字节字段,这通常是一种安全的更改。

2、protobuf 中bytes与string在C++中的区别

protobuf中有stringbytes两种数据类型, 相对应于python中的 stringbytes类型。但在C++中有std::string 却没有bytes类型。他们之间怎么转换。

看了一些介绍得到的结论是:

  • C++中,protobufstring类型和bytes类型都对应与**C++**的std::string类型。

  • 区别是,protobufstring 对应的 std::string 类型需进行UTF-8字符的检查,而bytes对应的std::string类型三不进行UTF-8字符检查。

protobuf提供了多种基础数据格式,包括string/bytes。从字面意义上,我们了解bytes适用于任意的二进制字节序列。然而对C++程序员来讲,std::string既能存储ASCII文本字符串,也能存储任意多个**\0**的二进制序列。那么区别在哪里呢?

同时在实际使用中,我们偶尔会看到类似这样的运行错误:

[libprotobuf ERROR google/protobuf/wire_format.cc:1091] String field 'str' contains invalid UTF-8 data when serializing a protocol buffer. Use the 'bytes' type if you intend to send raw bytes.
 
[libprotobuf ERROR google/protobuf/wire_format.cc:1091] String field 'str' contains invalid UTF-8 data when parsing a protocol buffer. Use the 'bytes' type if you intend to send raw bytes.

在之前的文章里介绍过protobuf序列化的过程,我们看下**string/bytes**序列化的过程。

所有的序列化操作都会在**SerializeFieldWithCachedSizes这个函数里进行。根据不同的类型调用对应的序列化函数,例如对于string**类型:

case FieldDescriptor::TYPE_STRING: {
 
	string scratch;
 
	const string& value = field->is_repeated() ?
 
	message_reflection->GetRepeatedStringReference(message, field, j, &scratch) ;
 
	message_reflection->GetStringReference(message, field, &scratch);
 
	VerifyUTF8StringNamedField(value.data(), value.length(), SERIALIZE,
 
	field->name().c_str());
 
	WireFormatLite::WriteString(field->number(), value, output);
 
	break;
 
}

而对于**bytes**类型:

case FieldDescriptor::TYPE_BYTES: {
 
	string scratch;
 
	const string& value = field->is_repeated() ?
 
	message_reflection->GetRepeatedStringReference(message, field, j, &scratch) ;
 
	message_reflection->GetStringReference(message, field, &scratch);
 
	WireFormatLite::WriteBytes(field->number(), value, output);
 
	break;
 
}

可以看到在序列化时主要有两点区别:

  • **string**类型增加调用了VerifyUTF8StringNamedField函数。
  • 序列化函数不同:WriteString vs WriteBytes

关于第二点,两个函数都定义在**wire_format_lite.cc**,实现是相同的。

那么我们继续看下第一点,VerifyUTF8StringNamedField调用了VerifyUTF8StringFallback(话说一直不理解fallback在这里什么意思,protobuf源码里经常看到这个后缀)。看下这个函数的实现:

void WireFormat::VerifyUTF8StringFallback(const char* data,int size,Operation op,const char* field_name) {
 
	if (!IsStructurallyValidUTF8(data, size)) {
 
		const char* operation_str = NULL;
 
		switch (op) {
 
		case PARSE:
 
			operation_str = "parsing";
 
		break;
 
		case SERIALIZE:
 
			operation_str = "serializing";
 
		break;
 
		// no default case: have the compiler warn if a case is not covered.
 
	}
 
		string quoted_field_name = "";
 
		if (field_name != NULL) {
 
		quoted_field_name = StringPrintf(" '%s'", field_name);
 
		}
 
		// no space below to avoid double space when the field name is missing.
 
		GOOGLE_LOG(ERROR) << "String field" << quoted_field_name << " contains invalid "
 
		<< "UTF-8 data when " << operation_str << " a protocol "
 
		<< "buffer. Use the 'bytes' type if you intend to send raw "
 
		<< "bytes. ";
 
	}
 
}

运行错误是从这里输出的,关键还是在于**IsStructurallyValidUTF8这个函数,实现在structurally_valid.cc**里:

bool IsStructurallyValidUTF8(const char* buf, int len) {
 
	if (!module_initialized_) 
        return true;
 
 	int bytes_consumed = 0;
 
	UTF8GenericScanFastAscii(&utf8acceptnonsurrogates_obj,buf, len, &bytes_consumed);
 
	return (bytes_consumed == len);
 
}

这里逐个字符扫描是否符合UTF-8规范,比如**110xxxxx 10xxxxxx这样,具体可以参考UTF-8**的编码标准。

反序列化过程类似。

看到这里我们可以得到这样的结论:

  • protobuf里的**string/bytesC++接口里实现上都是std::string**。
  • 两者序列化、反序列化格式上一致,不过对于**string格式,会有一个UTF-8**格式的检查。

出于效率,我们应当在确定字段编码格式后直接使用**bytes,减少UTF-8**编码的判断,效率上会有提高。

注意以上代码在pb2.6下,2.4不会输出**field_name**。

据了解**java接口上有一定的区别,分别对应String以及ByteString**。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
可以使用iconv库中的iconv函数将UTF-8编码的string类型转换成Unicode编码的字节数组。具体步骤如下: 1. 引入iconv库头文件: ```c++ #include <iconv.h> ``` 2. 创建iconv_t类型的句柄: ```c++ iconv_t conv = iconv_open("UTF-32LE", "UTF-8"); ``` 其中,第一个参数是目标编码格式,第二个参数是源编码格式。本例中将UTF-8编码的string类型转换成UTF-32LE编码的字节数组。 3. 定义源字符串和目标缓冲区: ```c++ std::string str = "Hello, 世界!"; char* src = const_cast<char*>(str.c_str()); size_t srclen = str.length(); char* dst = new char[srclen * 4]; // 缓冲区大小为源字符串长度的四倍(UTF-32编码下每个字符占4个字节) size_t dstlen = srclen * 4; ``` 4. 调用iconv函数进行转换: ```c++ size_t res = iconv(conv, &src, &srclen, &dst, &dstlen); ``` 其中,第一个参数是iconv_t类型的句柄,第二个参数是源字符串的指针,第三个参数是源字符串的长度,第四个参数是目标缓冲区的指针,第五个参数是目标缓冲区的长度。 5. 关闭iconv句柄并释放资源: ```c++ iconv_close(conv); ``` 完整示例代码: ```c++ #include <iostream> #include <iconv.h> int main() { std::string str = "Hello, 世界!"; char* src = const_cast<char*>(str.c_str()); size_t srclen = str.length(); char* dst = new char[srclen * 4]; // 缓冲区大小为源字符串长度的四倍(UTF-32编码下每个字符占4个字节) size_t dstlen = srclen * 4; iconv_t conv = iconv_open("UTF-32LE", "UTF-8"); size_t res = iconv(conv, &src, &srclen, &dst, &dstlen); iconv_close(conv); std::cout << "转换结果:" << std::endl; for (size_t i = 0; i < srclen * 4 - dstlen; i += 4) { uint32_t ch = *(reinterpret_cast<uint32_t*>(dst + i)); std::cout << std::hex << ch << " "; } std::cout << std::endl; delete[] dst; return 0; } ``` 注意:上述代码中的转换结果是以16进制形式输出的字节数组,如果需要以Unicode字符串形式输出,可以在输出字节转换成Unicode字符,或者使用std::wstring类型存储转换结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

森明帮大于黑虎帮

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

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

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

打赏作者

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

抵扣说明:

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

余额充值