欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于字符集、字符编码与HTTP编码解码之万象详解

程序员文章站 2023-12-13 20:53:16
在日常编写代码过程中,常常会碰到乱码问题,一个典型的情况是浏览网页,如果网站开发者缺少经验,就会带来这种令人头疼的问题。要了解乱码的症结,我们就得从字符集和字符编码说起,先...

在日常编写代码过程中,常常会碰到乱码问题,一个典型的情况是浏览网页,如果网站开发者缺少经验,就会带来这种令人头疼的问题。要了解乱码的症结,我们就得从字符集和字符编码说起,先来看看它们到底是什么:
1:字符集:是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
2:字符编码:是一套法则,最常规的理解就是:让程序根据这个法则对应到相应的字符集中将byte[]存取为string。
现在,我们要来看看这些东西在 .net 中对应的是什么。

一:字符集和字符编码
如果想得到全部的字符集,则使用 system.text.encoding.getencodings() 方法,以下代码用于列出.net支持的全部字符集:

复制代码 代码如下:

foreach (var item in encoding.getencodings())
{
    console.writeline(item.name);
}

字符串在进行如网络传输等场景时,要先转为 byte[] 。但是,首先,不同的字符编码规则,所转换生成的byte[]是不一样的。所以,再将byte[]转换回string的时候,要依据原先的字符编码规则。有如下几种情况能导致“乱码”的产生:
1:string to byte[] 和 byte[] to string,使用了不同的字符编码规则;
2:byte[] to string 的 时候,当前宿主环境没有对应的字符集;

示例:

复制代码 代码如下:

string originalstring = "hello test, 测试!";
byte[] utf8bytes = encoding.utf8.getbytes(originalstring);
string utf8string = encoding.utf8.getstring(utf8bytes);
string errorstring = encoding.ascii.getstring(utf8bytes);

观察encoding类,实际上象上面utf8这样的属性,只有几个,这些是最常用的字符集,要获取其它,如gb2312这样的字符集,则需要象如下这样来获得:
复制代码 代码如下:

byte[] gbbytes = encoding.getencoding("gb2312").getbytes(originalstring);
string utf8string = encoding.getencoding("gb2312").getstring(gbbytes);

二:典型应用场景之 httpwebresponse

很多人都作过页面抓取功能, httpwebresponse 就会比较熟悉,当然如果不嫌麻烦,也可以用 socket 实现,但是同时要解析很多属性以及处理象重定向之类的诸多问题。
 

2.1 http header 和http content是什么?
浏览一个网页,使用很多工具,或者使用.net中的某些类进行抓取,都给我们结构化为 http 头和正文这样的信息,其实,当我们发送一个请求,服务器返回给我们的是一串 byte[],我们完全可以自己去从这串 byte[] 解析出 http header 和 http content,它们之间其实仅仅非常简单的以两个 /r/n/ 分割开而已,历史上有著名的crlf攻击,cr就是\r,lf就是\n,就利用的是这个规则。


2.2 我们如何察看http header,http content?
其实很简单,既然这些都是 byte[] ,所以,我们只要知道这段 byte[] 正确的字符编码规则,就能得到我们所需要看到的 html (html就是字符串而已)。使用 httpwebresponse 这个类,就能请求一个 url ,该类自动为我们解析出了 httpheader ,有意思的是,它没有给我们解析出 content ,所以,我们需要自己完成正文的byte[] to string。
 

2.3 http content to string的具体做法
好的,实际上,httpheader  中已经告诉了我们一些字符集编码相关的信息,我们可能感兴趣,以及会混淆的这些http头如下:

复制代码 代码如下:

content-type:web 服务器告诉浏览器自己响应的对象的类型和字符集。例如:content-type: text/html; charset='gb2312' ;
content-encoding:web 服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:content-encoding:gzip 。这里我要多说一点,这个 content-encoding 的 http header 会令人混淆,极度容易让人理解成是字符集或字符编码信息;

那么,这些 http 头在httpwebresponse 中是怎么代表的呢?

复制代码 代码如下:

httpwebresponse.content-type对应的是http头的content-type比如"text/html;"后的那个charset,实际是和httpwebresponse.charaterset是一致的。但是如果前者无,则后者

一般会指定一个默认的httpwebresponse.charaterset,默认为"iso-8859-1"。
httpwebresponse.contentencoding 代表的是 http头中 content-encoding,与此类似的,还有一个http头,为transfer-encoding。注意,很恶心的一点是

httpresponse.contentencoding跟httpwebresponse.contentencoding代表的不是一个东西,它和httpresponse.charaterset在msdn上是一致的解释。


根据上面的说法,似乎下面的代码就能得到http content的字符编码规则:
复制代码 代码如下:

return encoding.getencoding(
   string.isnullorempty(httpwebresponse.charaterset) ?
"iso-8859-1" : httpwebresponse.charaterset

但是,这里有一个很重要的但是,如果你尝试从http头或者httpwebresponse所给我的这些字符编码信息或属性去解码正文content的话,很可能马上就会迎来一个大大的挫折。我们很可能会发现以下几个可悲的事实:
复制代码 代码如下:

1:http头的content-type中没有charset信息;
2:httpwebresponse.charaterset是空的;
3:http头的content-type和httpwebresponse.charaterset是不一致的;
4:http头的content-type和httpwebresponse.charaterset是一致的,但是解码还是错的;
5:尝试用"iso-8859-1"解码也是错的。

2.4 为什么还是有乱码问题?bom能解决一切?

之所以碰到以上问题,其实仅仅是因为,服务器给我们传回来的是byte[],而任何程序员在写服务器端web程序的时候,都有可能有意或无意的转码出不规范的byte[]来。所以,如果我们尝试从http头的content-type和httpwebresponse.charaterset想要得到编码规则,我们就败了,我们败在了有标准,但是没人严格去执行标准。

有一些颇具迷惑性的api试图在告诉我们,使用我你就能得到该流正确的encoding了,比如,streamreader.currentencoding,我们可以把httpwebresponse的getresponse中读取到

byte[],放置到memorystream中,然后利用如下代码:

复制代码 代码如下:

streamreader sr = new streamreader(memorystream, true)
return sr.currentencoding;

似乎就可以得到encoding了,其实非也,注意streamreader构造器的第二个参数,为detectencodingfrombyteordermarks。byteordermarks是什么呢?解释如下:
复制代码 代码如下:

bom(byte-order mark),即字节顺序标记,它是插入到以utf-8、utf16或utf-32编码unicode文件开头的特殊标记,用来识别unicode文件的编 码类型。对于utf-8来说,bom并不是必须的,因为bom用来标记多字节编码文件的编码类型和字节顺序(big-endian或little- endian)。

这表明了什么呢?表明了如果你的字节流未含有bom,或者即便包含了bom,但是字节流不是unicode-based的encoding,则依旧不能得到正确的encoding,具体我们也可以看streamreader的源码来得到验证。这个万恶的currentencoding属性并没有告诉你它的前提条件。


2.5 关于本例的一点补充
以上字节流的编码解码,很多地方用了response做例子,但是,以上解码针对的是非压缩的response,如果服务器已经对http流进行了压缩(其压缩格式在content-encoding中指明了),我们就得先解压缩,再解码response流,然后再解码正文。考虑到本文的主题,特意剪裁了对于 response 流的解压过程。


2.6 关于正确解码的尝试

有很多人尝试从byte[]本身去解析和判断编码规则的api,如:codeproject上也有相关的文章,但是可悲的事实是:并没有一种完美的方法来自动判断byte[]的编码规则。还记得我们的浏览器(如ie)的编码设置中的“自动选择”吗,其实这个自动选择的错误率还是蛮高的。所以,对于字节流的生成者,如bs程序开发者,可以通过规范输出:声明charset和编码规范的方式,这样才能让解析者(如浏览器)解析的时候尽可能的少出现乱码。 

上一篇:

下一篇: