最近在写网络通信上的一些东西,快被这些编码格式搞崩溃了。
一、什么是编码
编码是对现有“符号”进行转化,可以存储在计算机中,在没有计算机时,我们的使用的“符号”,都是手写的,我们的大脑对其编码,这样我们就能记住和识别。但计算机只能存储电信号,即二进制。所以,我们需要对其编码,能使计算机储存。
各个国家和地区所制定了不同 ANSI 编码标准中,都只规定了各自语言所需的“字符”。这样就不利于交流,所以就有了Unicode编码。
名词解析:
1.ANSI:众所周知,刚开始计算机是在美国出现的,所以他们也是第一批考虑编码的,第一开始由于只是用于计算,所有,只用了ASCII码,但后来计算机飞速发展,已经不仅仅用于计算。所以美国就提出了ASNI标准来进行编码。后来,中日韩等又提出了自己的编码编码,例如中国的GBK,但还是统称为ANSI标准。因此它是一个编码集,并不是特定指一种编码格式。
2.Unicode:这个又称万国码,设计的初衷就是设计出一种通用的编码集。
3.UTF-8:这是Unicode的一种实现方式。Unicode设计初衷是好的,但是由于是万国码,所以要用4个字节来进行编码,但是,如果使用Unicode编码,存储英文字符和数字时,就会浪费存储空间,因此需要使用一种能减少存储空间的方法。UTF-8就应运而出。
二、utf-8具体实现。
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
----------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
其中x填入的即是对应的Unicode编码。
UCS-2和UCS-4
Unicode是为整合全世界的所有语言文字而诞生的。任何文字在Unicode中都对应一个值,这个值称为代码点(code point)。代码点的值通常写成 U+ABCD 的格式。而文字和代码点之间的对应关系就是UCS-2(Universal Character Set coded in 2 octets)。顾名思义,UCS-2是用两个字节来表示代码点,其取值范围为 U+0000~U+FFFF。
为了能表示更多的文字,人们又提出了UCS-4,即用四个字节表示代码点。它的范围为 U+00000000~U+7FFFFFFF,其中 U+00000000~U+0000FFFF和UCS-2是一样的。
要注意,UCS-2和UCS-4只规定了代码点和文字之间的对应关系,并没有规定代码点在计算机中如何存储。规定存储方式的称为UTF(Unicode Transformation Format),其中应用较多的就是UTF-16和UTF-8了。
三、怎么对编码进行转换。
首先要明确一个概念,由于网络中大部分都是利用utf-8进行编码的(这是因为utf-8通用,且同样的内容所占字节更少)。我们所谓的转换编码,大部分是转换到utf-8利于传输,但utf-8仍然属于Unicode编码,所以就会有这样一个逻辑关系。
ANSI(GBK)需先转换到 Unicode,从Unicode在转换为utf-8,反之亦然。
首先是ANSI转换为Unicode:这一步,是我刚开始最不理解的。因为底层编码都是不同。利用汉字“严”。其Unicode编码为 4E25,但其GBK 编码为:D1CF。查了一点资料,发现和自己想的一样,是利用对应的编码表转换。
其次是Unicode转换为UTF-8。这一步,了解到具体的UTF-8的构成,我们就可以很容易搞懂其具体原理。
四、我所找到的函数
声明:以下函数均摘自别人博客。
1.UTF-8和GBK互转。
这与我前面所述,并无冲突。只是隐含了转到Unicode这一过程。这是利用 WindowsAPI来实现的。
传送门:
https://blog.csdn.net/xiaohu\_2012/article/details/14454299
#include<windows.h>
#include<string>
std::string UTF8ToGBK(const char* strUTF8)
{
int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, NULL, 0);
wchar_t* wszGBK = new wchar_t[len + 1];
memset(wszGBK, 0, len * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, wszGBK, len);
len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
char* szGBK = new char[len + 1];
memset(szGBK, 0, len + 1);
WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
std::string strTemp(szGBK);
if (wszGBK) delete[] wszGBK;
if (szGBK) delete[] szGBK;
return strTemp;
}
std::string GBKToUTF8(const char* strGBK)
{
int len = MultiByteToWideChar(CP_ACP, 0, strGBK, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len + 1];
memset(wstr, 0, len + 1);
MultiByteToWideChar(CP_ACP, 0, strGBK, -1, wstr, len);
len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
char* str = new char[len + 1];
memset(str, 0, len + 1);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
std::string strTemp = str;
if (wstr) delete[] wstr;
if (str) delete[] str;
return strTemp;
}
这里利用的是Windows API:
MultiByteToWideChar()
WideCharToMultiByte() 需要说明的vs编辑问题。https://blog.csdn.net/a3192048/article/details/82154194
工程属性里,字符集可以选择“使用Unicode字符集”和“使用多字节字符集”。此选项只控制代码里的API是用宽字符版(即Unicode)的还是ANSI字符版(即GBK)的,它控制不了代码里的字符是用Unicode编码还是ANSI编码。
如果选择了“使用Unicode字符集”,则代码里用到的API被解释为Unicode版本的API(带标记W的API),如MessageBox被解释为MessageBoxW;
如果选择了“使用多字节字符集”,则代码里用到的API被解释为ANSI编码版本的API(带标记A的API),如MessageBox被解释为MessageBoxA。
这里真是纠结半天,其实我们编写的代码页仍然是系统默认的编码集,Windows一般是gbk。还有你在vs中设置保存为 utf-8时,会发现你不能在程序中,使用汉字字符串。这也是vs最坑爹的地方。
2.UCS-2转utf-8。这就是Unicode转utf-8。
传送门:https://blog.csdn.net/go\_to\_learn/article/details/8048472
五、我的感悟
写了一个传输utf-8的简单通信,这个编码就能困扰我两天。c++对这些底层编码真是没有现成的库。不像Java那样封装好了。Java中一行代码就可以搞定的事,我自己折腾了好久。不过也真正懂得了编码的概念。以前经常混淆。
碰壁之处:
我的传输数据中有汉字,需要转码成utf-8进行传输,否则服务器会识别不出来我传输的数据。由于字符和英文属于127的字符。Unicode和GBK都是相同的。不存在转码问题。只有汉字需要转码。
解决方法:
在封装数据包时,将汉字转码为utf-8格式。由于 特殊字符和英文在utf-8和gbk中都相同。所以我们封装的数据包就相当于utf-8的数据包,虽然我们只转码了汉字。这样服务器就可以正常解析了。