記事ディレクトリ
1. プロトコル バッファの解析時に無効な UTF-8 データが含まれています。生のバイトを送信する場合は、「バイト」タイプの質問を使用してください
プロトコル バッファの解析時に無効な UTF-8 データが含まれています。生のバイトを送信する場合は、「bytes」タイプを使用します。
C++クライアントを使用してC++サーバーにリクエストを送信します。
私たちが使用したプロトパラメータは次のように定義されています。
しかし、プロトコル バッファの解析時に無効な UTF-8 データが含まれているというエラーが発生します
。生のバイトを送信する場合は、「bytes」タイプを使用します。
message PbRider {
string speed_param = 28;
};
しかし、プロトコル バッファの解析時に無効な UTF-8 データが含まれているというエラーが発生します
。生のバイトを送信する場合は、「bytes」タイプを使用します。
C++ grpc クライアントを使用して、C++ サーバーにリクエストを送信します。
使用するプロトタイプ パラメーターは次のように定義されています:
message PbRider { string Speed_param = 28; };しかし、エラーが発生します: プロトコル バッファーの解析に無効な UTF-8 データが含まれています。生のバイトを送信する場合は、「バイト」タイプを使用してください。
文字列フィールドは文字列UTF-8 データのみを対象としているため、非 UTF-8 データを送信する必要がある場合は、bytes
代わりにフィールドを使用することをお勧めします。
字节
文字列フィールドは UTF-8 データを保存するためにのみ使用されるため、非 UTF-8 データを送信する必要がある場合は、代わりのフィールドを使用することをお勧めします。
しかし、 C++サーバーではデータが type-string に変更されているのに、C++クライアントがこのデータを解析できないのはなぜですか? おそらく、C++ protobuf は、非 UTF-8 データを含むがproto で文字列として定義されているデータに対処するために何かをすべきでしょうか?
しかし、C++ サーバー上のデータは type-string に変更されているのに、なぜ C++ クライアントはこのデータを解析できないのでしょうか? おそらく、非 UTF-8 データを含むが proto で文字列として定義されているデータを処理するために C++ protobuf が行うべきことがあるでしょうか?
少なくともC++では、文字列が解析中に UTF-8 であることを検証しますが、シリアル化中には検証しません。したがって、C++ が同様のことを行う場合、非 UTF-8文字列を使用してプロトをシリアル化することが可能になります。これは、そのプロトが再度解析されるまで検出されません。
そのフィールドに非 UTF-8 データを保存できるようにする必要がある場合は、文字列フィールドからバイトフィールドに変更することを検討するとよいでしょう。これは通常、安全な変更です。
少なくとも C++ では、シリアル化時ではなく、解析時に文字列が UTF-8 であることを確認すると思います。したがって、C++ が同様のことを行った場合、非 UTF-8 文字列を使用してプロトをシリアル化することが可能になりますが、プロトが後で再度解析されるまで検出されません。
そのフィールドに非 UTF-8 データを格納できるようにする必要がある場合は、文字列フィールドからバイト フィールドに変更することを検討するとよいでしょう。これは通常は安全な変更です。
2. C++ の protobuf におけるバイトと文字列の違い
protobufにはstringとbytesという 2 つのデータ型があり、 pythonのstring型とbytes型に対応します。しかし、C++にはstd::string はありますが、バイト型はありません。それらを切り替える方法。
いくつかの紹介文を読んだ後の結論は次のとおりです。
-
C++では、protobufの文字列型とバイト型は **C++** の std::string 型に対応します。
-
違いは、protobufのstringに対応するstd::string型はUTF-8文字についてチェックする必要があるのに対し、bytesに対応するstd::string型はUTF-8文字についてチェックする必要がないことです。
protobuf は、 string/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;
}
シリアル化には 2 つの主な違いがあることがわかります。
- **
string
** 関数と呼ばれるインクリメントを入力しますVerifyUTF8StringNamedField
。 - シリアル化関数は異なります
WriteString vs WriteBytes
。
2点目に関しては、どちらの関数も**で定義されておりwire_format_lite.cc
、実装も同様です。
次に、最初のポイントを見て、VerifyUTF8StringNamedField
それを呼び出してみVerifyUTF8StringFallback
ましょう(ここでのフォールバックの意味を私はまったく理解していませんでした。この接尾辞は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の ** はすべてC++インターフェイスの実装
string/bytes
の** です。std::string
- 2 つのシリアル化および逆シリアル化形式は一貫していますが、**
string
形式の場合は、UTF-8 ** 形式チェックが行われます。
効率を考えると、フィールドのエンコード形式を決定した直後に ** を使用しbytes
、 UTF-8** エンコードの判定を減らすと効率が向上します。
上記のコードは pb2.6 の下にあるため、2.4 では ** field_name
** が出力されないことに注意してください。
**インターフェイスには、それぞれと** に対応するjava
特定の違いがあることが理解されています。String
ByteString