signed

QiShunwang

“诚信为本、客户至上”

protobuf解析出错:Windows与Linux通信之string乱码

2021/6/9 7:44:24   来源:

现象


Windows上程序与Linux上程序通信,具体为Windows程序读取机器上一个文件,把数据信息发送到Linxu进程。

Linxu进程负责把结构体使用protobuf编码,写入redis,然后在有人请求的时候读出来展示。

问题就出在了,编码和写入都成功了,查询的时候却报pb解析失败,一时摸不到头脑。

定位及解决


经过跟踪和分析,结构体中有一string,当windows进程读取到的该字段为英文时,到Linux进程中显示是正常的。

当该字段为中文时,Linux进程日志中显示该字段为乱码:

在这里插入图片描述

定位是比较简单的,根据前述问题描述,基本就可以断定是Windows和Linux编码方式不同所致。

一般而言,Windows中文采用的是GBK编码,而Linux上我统一采用的是utf-8编码。

所以解决方法就是,在Windows进程读取该字段后,通过字符转换把GBK转码为utf-8,示例代码如下:

#include <locale>
#include <codecvt>

#ifdef _MSC_VER
	const string kGbkLocaleName = ".936";
#else
	const string kGbkLocaleName = "zh_CN.GBK";
#endif

std::string CatsMgr::Gbk2Utf8(const std::string& str)
{
    std::wstring_convert<std::codecvt_byname<wchar_t, char, mbstate_t>> convert(new std::codecvt_byname<wchar_t, char, mbstate_t>(kGbkLocaleName));
    std::wstring tmp_wstr = convert.from_bytes(str);

    std::wstring_convert<std::codecvt_utf8<wchar_t>> cv2;
    return cv2.to_bytes(tmp_wstr);
}

经过这个转码后,Linux进程显示中文也正常了。

扩展


在涉及跨平台的进程间通信时,如果双方使用的编码格式不一致,常常会造成一些乱码显示不了的问题。

c++11中提供了字符编码转换的功能,可以解决我们平时开发中字符编码转换的需求。主要使用wstring_convert和codecvt相结合进行转换。

包含的头文件为:

#include <locale>
#include <codecvt>

注意:

  • windows平台的std::wstring 就是std::u16string, wchar_t 就是char16_t (utf-16编码)。window平台的终端编码一般是gbk。
  • linux平台的std::wstring就是std::u32string, wchar_t 就是char32_t (utf-32编码)

下面提供几个常用的转换。

  • gbk转utf-8:
// 先转化为std::wstring,
std::string gbk_to_utf8(const std::string& str)
{
	//GBK locale name in windows
	const char* GBK_LOCALE_NAME = ".936";
	std::wstring_convert<std::codecvt_byname<wchar_t, char, mbstate_t>> convert(new std::codecvt_byname<wchar_t, char, mbstate_t>(GBK_LOCALE_NAME));
	std::wstring tmp_wstr = convert.from_bytes(str);
 
	std::wstring_convert<std::codecvt_utf8<wchar_t>> cv2;
	return cv2.to_bytes(tmp_wstr);
}
  • utf-8转gbk:
std::string utf8_to_gbk(const std::string& str)
{
	std::wstring_convert<std::codecvt_utf8<wchar_t> > conv;
	std::wstring tmp_wstr = conv.from_bytes(str);
 
	//GBK locale name in windows
	const char* GBK_LOCALE_NAME = ".936";
	std::wstring_convert<std::codecvt_byname<wchar_t, char, mbstate_t>> convert(new std::codecvt_byname<wchar_t, char, mbstate_t>(GBK_LOCALE_NAME));
	return convert.to_bytes(tmp_wstr);
}
  • gbk转std::wstring:
std::wstring gbk_to_wstr(const std::string& str)
{
	//GBK locale name in windows
	const char* GBK_LOCALE_NAME = ".936";
	std::wstring_convert<std::codecvt_byname<wchar_t, char, mbstate_t>> convert(new std::codecvt_byname<wchar_t, char, mbstate_t>(GBK_LOCALE_NAME));
	return convert.from_bytes(str);
}
  • std::string 转为 std::wstring( utf-8 --> wchar ):
// 要求std::string的编码是utf-8,不然会抛异常
std::wstring utf8_to_wstr(const std::string& src)
{
	std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
	return converter.from_bytes(src);
}
  • std::wstring转为std::string(wchar --> utf-8):
// 获得的std::string的编码为utf-8,windows下输出是乱码 (windows终端输出中文要正常显示,要转化为GBK编码)
std::string wstr_to_utf8(const std::wstring& src)
{
	std::wstring_convert<std::codecvt_utf8<wchar_t>> convert;
	return convert.to_bytes(src);
}

注意,GBK在linux下的locale名可能是"zh_CN.GBK",而windows下是".936",因此做跨平台的话仍然要给不同的系统做适配。

小结


遇到乱码显示的情况,不要慌乱,也不要坐视不管,因为早晚都要管。

所以在遇到乱码的问题时,第一时间分析解决,只需要几行代码,就可以解决所有问题了。

参考

https://blog.csdn.net/qq_31175231/article/details/83865059