九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

windows/linux服务器程序支持库的开发(2)--序列化支持(1)


windows/linux服务器程序支持库的开发2--序列化支持(1)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

没有工作前,不知道序列化的作用。。。。虽然那时候学习《Programming with MFC》的时候,也知道CArchiveCObject等东西,但是没有太意识到其作用。但是,如前所述,我工作的第一件事情,就是了解公司的序列化类。从当时的一无所知,到现在也慢慢理解了一些序列化的作用了。说起来,自从工作以来因为当时做文件系统后后来的程序补丁,我应经在公司的序列化类以外额外实现了两个独立的序列化类了,分别是文件序列化和内存序列化。

这里也顺便讲讲序列化吧,虽然不算太难,也不算太复杂,但是这样越是基础的东西越是支持着程序:)没有还真不行。

序列化的作用包括,实现操作系统,硬件平台无关的数据保存和传输。从网络传输角度来讲解决诸如大头,小头等问题。目前我自己的感受还有,对于保存数据和读取数据使用同一套接口,简化了数据结构的管理,方便使用。虽然,序列化的作用一般是用来做永久保存的,在《深入浅出MFC》中,侯捷就将其中的序列化技术讲解称为“VC++六大关键技术”,事实上,序列化的作用不仅仅如此,比如我以前不就写过一个内存序列化类吧:)公司还有一个网络序列化类,即便在MFC中,也有可以序列化的CMemFile,事实上,序列化还是一种二进制的交流方式。:)

最典型的网络程序,就更好理解了,网络上交流的其实都是二进制的数据,从一端到另外一端,大家要有统一的格式支持才能和谐-_-!不仅仅是字符串的编码需要一致,字节的顺序需要一致,每个位置是什么值自然也需要一一指定好,一般而言,对于网络程序,在我们公司,对于序列化成一个包,我们叫打包,从一个网络包反序列化(似乎也有人叫串行化),我们叫解包。

这种二进制交流的方式不仅限于网络程序,任何程序间的交流都有可能用到,我碰到另外一个典型情况是一个Python脚本希望将一些数据经过一段C++程序,然后再放到另外一段Python脚本中去,这时当然可以通过PythonC API将所有的数据都一个一个转成C变量保存下来然后传出去,由C++程序运行时保存,然后再通过PythonC API一一转成Python的变量,这样做有很多不好的地方,其一是Python脚本间传递的数据对于C++程序来说不透明,任何改变都需要C++程序进行相应的更改,另外是每个变量都通过PythonC API去转换效率不高。其实这里就可以利用序列化成二进制数据来传输,其上两个缺点都会消失。顺便提一下,在Python中可以通过struct库的pack,unpack函数进行简单数据的序列化,通过marshal库来进行复杂对象的序列化(可是这个库好像已经不被推荐,但是我当时在实际工作中使用的就是这个),有更先进的pickle(有更快的C语言版本cPickle)shelve可用。从这点来看,又会感叹Python的库实在是太多,往往不是没有你想要的库,而是由于库太多,你不知道哪个好用-_-!

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

UCS-2与UTF8之间的选择(5)--断然决定UTF-8


UCS-2UTF8之间的选择(5--断然决定UTF-8

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

       想来想去,在Unicode这种问题上纠缠过多似乎意义不大,就如我以前说的,我认为将来很自然的不会再需要考虑这个问题,因为,未来总会统一到UTF-32上去,那时候我现在考虑的这些东西都是完全没有意义-_-!在一个不久的将来就会完全没有意义的事情上考虑来考虑去似乎太没有抓住主要问题......

       最后我的决定是使用UTF-8,这是个背Windows的选择,但是是个亲开源的选择,亲近世界的选择......即便到了现在,世界上还是有很多程序员不使用Unicode,即便是在Windows下编程。

       举个让人信服的例子就是breakpadgoogle-breakpad - Google Code),这个程序对错误的处理技术可谓手段用尽,为了在程序出错下不破坏现场的手段现在还感叹精巧,不能说开发人员没有水平,但是,在Unicode模式下编译就会有严重的问题(我去年刚开始的第一个工作就是这个,现在不知道修复了没有)。只能感叹英语的强势,无论是在文化领域还是在编程领域都是一样。。。。。对于更多的开源项目,可以想象情况怎么样。。。。。

       另外,即便是一般的开源项目考虑了国际化,选择了Unicode来进行开发,一般而言,就目前的情况来看,似乎也是UTF-8居多,因为以前谈到的那么多的UTF-8的优点,其中最最符合欧美人习惯的一点就是完全兼容ASCII。。。。作为以自己语言开发的项目,移植到UTF-8一般而言比移植到UCS-2要更加方便,再加上开源世界的主导GNU/Linux的核心Linux的核心(好像有点绕)就是UTF-8的,所以,开源软件一般使用UTF-8也就很好理解了。就个人的开发学习而言,肯定会用到很多开源软件,或者参考,或者直接复用,当我的开发也是以UTF-8为基础的话,可以省事很多。举个简单的例子就是,CEGUI就是UTF-8的,这点我甚至看到Windows下的开发人员骂过,但是,这就是世界的现实。另外,就我使用的经验而言,MySQL也是对UTF-8的支持更加好,虽然其支持UCS-2编码的字符串,但是在命令行控制时无法正确显示,MySQL的著名前端工具,PhpAdmin也是对UTF-8支持良好,一样也无法显示UCS-2编码的字符串。(这些都是基于以前工作中的经验,现在不一定准确)作为个人开发。。。。我好像最可能使用的数据库也就是MySQL了。。。SQL Server我用过,的确是对UCS-2支持的更好,但是即便实在要用,还是可以现转成UCS-2的。Oracle没有用过,我不做评论。

       另外,本人在公司常常是在VS2005下做Windows的开发,虽然也有Linux的工作,但是一般都仅仅是将Windows下开发好的服务器移植到Linux下的工作,其实对Linux下的学习不够深入,对Linux下的服务器开发学习也不够深入,而且以前说过,公司为了更加满足Windows下的客户端的要求,所以公司都是使用UCS-2作为编码,即便是Linux下的服务器都是强制使用了2字节的wchar_t来适应这种选择,也逼迫公司需要重编译所有其想要在Linux下使用的库。。。因为也需要这些库使用2字节的wchar_t,其实代价也挺大的。。。。而且作为学习,要是老是和工作中做一样的事情又怎么能更多的学到新东西呢,又何谓工作外的学习。。。。。再加上目前个人对Linux有很大的兴趣,希望多学点东西,这也是原因之一,虽然我的目标是可移植,但是感觉以后可能还是以在Linux下跑服务器为主,那么简化Linux下的开发,并优化速度,也算是理由之一吧。

       再说,习惯了VS的我一直希望能将自己搬迁到Eclipse这样的多功能开放平台上,(虽然VS好像也可以作为开放式平台,但是相对来说插件的选择和质量实在和Eclipse不是一个量级的),虽然我目前没有学习JAVA,但是Eclipse也是我感觉非常合适的开发平台,不仅仅当我想使用C时,使用C++时,当我想使用Python,Lua时,这一样也是个很好的平台,而且,最重要的是,Linux下也能使用,在Linux下还能用其来开发bash脚本:)这一点是VS怎么也不可能达到的。。。。(我不期望MS将来开发Linux下的VS),另外,EclipseUTF-8的支持很好,而且也有vi的插件可用(Eclipse下的vi插件没有VS下的这么强大,支持的功能比较弱。。。这点比较遗憾。。。。),何况,假如哪天我希望学习JAVA。。。那么连IDE学习的时间都省了。。。。对于开发工具。。。实在可以有很多话说,越是学的语言更多,越是觉得为每一种语言去学习一个开发平台是不值得的,假如有一个比较统一的平台,那样可以节省很多时间,目前来说,最最能够胜任这个目标的就是Eclipse了,感谢IBM。。。。。目前我的暂时替代品就是VIM...文本编辑之王,写任何程序都感觉非常爽,就是配置起来有点麻烦,除了编辑以外的事情处理起来效率稍微低了点,调试更是弱项。。。。虽然其一向贯彻着一个程序只做好一件事情的原则,让其编辑能力无可匹敌(真的好用),但是当我需要更多丰富功能的时候。。。。实在有点麻烦。。。呵呵,我前段时间学习的时候,基本都是用vim编辑,用gdb/pydb/bashdb调试(没有找到好用的LUA调试工具,这也是我最近很少用LUA的原因,甚至EclipseLUA开发插件都不支持调试-_-!)对了,还有汇编,VS2005即便加上VA,汇编程序的开发也没有任何帮助。。。。这在我前段时间学习反汇编和内嵌汇编的时候感受很深,希望Eclipse不也要这样。。。。呵呵,也许额外还要多说一下Eclipse的是,多多使用GCC对于简化移植会有更多帮助,唯一的麻烦可能就是很多人可能会排斥不能用VS编译的程序-_-!这就是MS垄断的影响力所在。

       似乎好像谈多了Linux就自然会讲到很多MS的坏话-_-!这几乎成了Linux社团的特点。。。。其实我并不讨厌Windows,用某人的话来说,即便某天Windows不再垄断,仅仅占有30%的市场份额,为Windows做开发的程序员仍然有广阔的空间。。。。何况现在呢-_-!我仅仅是刚开始学编程,希望多接触到一些思想,另外为了学习编程,需要看更多好的优秀的程序,而那些开源的优秀的程序往往是运行在Linux下的,Windows下的优秀程序虽然很多,但是苦于人家不让我看源码啊。。。。-_-!

唯一还需要看的问题是Python的支持,要是Python仅仅支持UCS-2,那么我还是会改变我的决定。可惜:)Python的默认Unicode实现就是UTF-8编码的,更为强大的是,Python的正则表达式库还对其UTF-8有直接支持,太强大了,这一直被视为UTF-8编码的弱项,那么,还缺少什么?万事俱备,连东风都不欠了。

对了,作为我关注的语言之一,LUA直到5.1版本都没有对Unicode有直接的支持。。。这点比较遗憾。至于Bash...我要他支持Unicode干什么-_-!

 

       当仅在windows来进行编码转换时,就可以利用Windows的编码转换APIWideCharToMultiByteMultiByteToWideChar函数对,还算是比较好用,起码比Unicode组织提供的好用的多。其实还有C 运行库mbtowc, wctomb可以用,这两个函数在Linux下也有实现。那么,以后就不在这个问题上再进行更多的纠缠了,UTF-8吧:)

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

UCS-2与UTF8之间的选择(4)--linux中各编码字符串的C/C++输出支持及方式比较


UCS-2UTF8之间的选择(4--linux中各编码字符串的C/C++输出支持及方式比较

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

继续研究UTF8UCS-2的选择,这里继续使用上一次提到的函数。

鉴于大家不一定能找到下载的地址,而源文件是允许自由散发的,我将代码打包,提供给大家下载,下载地址还是在讨论新闻组及文件中,名字为unicodeorg.rar

 

昨天看了下Windows下的方法,这次研究Linux下的:

 1 #include <stdio.h>
 2 #include <locale.h>
 3 #include <stdlib.h>
 4 #include "ConvertUTF.h"
 5
 6 int main(int argc, char* argv[])
 7 {
 8
 9     ConversionResult result = sourceIllegal;
10     UTF16 utf16_buf[3] = {0};
11     utf16_buf[0] = 0x4e2d;
12     utf16_buf[1] = 0x6587;
13     utf16_buf[2] = 0;
14     UTF16 *utf16Start = utf16_buf;
15     UTF8 utf8_buf[12] = {0};
16     UTF8* utf8Start = utf8_buf;
17
18     // If you want to test next line, you can't get anything but ???
19     // wprintf("%s/n", utf16_buf);
20
21     result = ConvertUTF16toUTF8((const UTF16 **) &utf16Start, &(utf16_buf[3]), &utf8Start, &(utf8_buf[12]), strictConversion);
22     switch (result) {
23         default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
24         case conversionOK: break;
25         case sourceExhausted: printf("sourceExhausted/t"); exit(0);
26         case targetExhausted: printf("targetExhausted/t"); exit(0);
27         case sourceIllegal: printf("sourceIllegal/t"); exit(0);
28     }
29
30     int i = 0;
31     for(; i < 12; ++i)
32     {
33         printf("%x ", utf8_buf[i]);
34     }
35     
36     printf("/n");
37
38     printf("%s/n", (char*)utf8_buf);
39     bzero(utf16_buf, sizeof(utf16_buf));
40
41     UTF8* utf8End = utf8Start;
42     utf8Start = utf8_buf;
43     utf16Start = utf16_buf;
44
45     result = ConvertUTF8toUTF16((const UTF8 **) &utf8Start, utf8End, &utf16Start, &(utf16_buf[3]), strictConversion);
46     switch (result) {
47         default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
48         case conversionOK: break;
49         case sourceExhausted: printf("sourceExhausted/t"); exit(0);
50         case targetExhausted: printf("targetExhausted/t"); exit(0);
51         case sourceIllegal: printf("sourceIllegal/t"); exit(0);
52     }
53
54     // If you want to test next line, you can't get anything
55     wprintf("%s/n", utf16_buf);
56
57     return 0;
58 }
59

 

运行结果:

e4 b8 ad e6 96 87 0 0 0 0 0 0

中文

 

这里和Windows中不同的就是输出UTF-8的字符串在Linux下甚至不需要通过setlocale设置环境变量,这样对于C++的输出估计还是有好处的:),起码不会去影响到C++的正常输出。但是,对于宽字节的输出没有办法成功,就算你像在Windows中设置locale也没有用,道理也很简单,因为Linux下的locale我就是设置成UTF-8-_-!

 

 1 #include <stdio.h>
 2 #include <locale.h>
 3 #include <stdlib.h>
 4 #include <iostream>
 5 #include "ConvertUTF.h"
 6 using namespace std;
 7
 8 int main(int argc, char* argv[])
 9 {
10     ConversionResult result = sourceIllegal;
11     UTF16 utf16_buf[3] = {0};
12     utf16_buf[0] = 0x4e2d;
13     utf16_buf[1] = 0x6587;
14     utf16_buf[2] = 0;
15     UTF16 *utf16Start = utf16_buf;
16     UTF8 utf8_buf[12] = {0};
17     UTF8* utf8Start = utf8_buf;
18
19     result = ConvertUTF16toUTF8((const UTF16 **) &utf16Start, &(utf16_buf[3]), &utf8Start, &(utf8_buf[12]), strictConversion);
20     switch (result) {
21         default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
22         case conversionOK: break;
23         case sourceExhausted: printf("sourceExhausted/t"); exit(0);
24         case targetExhausted: printf("targetExhausted/t"); exit(0);
25         case sourceIllegal: printf("sourceIllegal/t"); exit(0);
26     }
27
28     int i = 0;
29     for(; i < 12; ++i)
30     {
31         printf("%x ", utf8_buf[i]);
32     }
33     
34     printf("/n");
35
36     cout << (char*)utf8_buf <<endl;
37     bzero(utf16_buf, sizeof(utf16_buf));
38
39     UTF8* utf8End = utf8Start;
40     utf8Start = utf8_buf;
41     utf16Start = utf16_buf;
42
43     result = ConvertUTF8toUTF16((const UTF8 **) &utf8Start, utf8End, &utf16Start, &(utf16_buf[3]), strictConversion);
44     switch (result) {
45         default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
46         case conversionOK: break;
47         case sourceExhausted: printf("sourceExhausted/t"); exit(0);
48         case targetExhausted: printf("targetExhausted/t"); exit(0);
49         case sourceIllegal: printf("sourceIllegal/t"); exit(0);
50     }
51
52     return 0;
53 }
54

 

直接可以获得

e4 b8 ad e6 96 87 0 0 0 0 0 0

中文

的输出,也不需要调用C++locale(imbue)函数去改变流的状态,甚至可以说,使用了UTF-8不仅仅是对ASCII完全的兼容了,对于新添加的字符也是可以不改变任何代码就做到兼容的,这点给我印象深刻。难怪UTF-8虽然是变长的编码方式,但是还是获得了这么广范围的应用。

这样的情况下,在Linux下同时在C C++中使用UTF-8输出中文也不会有任何冲突,这个优势比在Windows下大多了。

另外,有个很重要的问题需要提及的就是,目前我使用的Ubuntu8.04桌面版,不支持全Unicode的编码,似乎中文是没有什么问题,但是以前那几个太玄经的字符是输出不了的,可能其Unicode的支持只到到BMP这一层,这点比Windows稍逊色。以前测试过,的确是输出不了,但是源代码已经被覆盖了,大家可以自己去确定一下:)

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

UCS-2与UTF8之间的选择(3)--windows中各编码字符串的C/C++输出支持及方式比较


UCS-2UTF8之间的选择(3--windowslinux中各编码字符串的C/C++输出支持及方式比较

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

继续研究UTF8UCS-2的选择,这里继续使用上一次提到的函数。

鉴于大家不一定能找到下载的地址,而源文件是允许自由散发的,我将代码打包,提供给大家下载,下载地址还是在讨论新闻组及文件中,名字为unicodeorg.rar

 

另外,在wiki上看到了一个对UTF8的评价:

优点及缺点

关于字符串长度的一个注解:

总体来说,在Unicode字符串中不可能由码点数量决定显示它所需要的长度,或者显示字符串之后在文本缓冲区中光标应该放置的位置;组合字符、变宽字体、不可打印字符和从右至左的文字都是其归因。

所以尽管在UTF-8字符串中字元数量与码点数量的关系比UTF-32更为复杂,在实际中很少会遇到有不同的情形。

  • 总体
    • 优点
      • UTF-8ASCII的一个超集。因为一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。为传统的扩展ASCII字符集设计的软件通常可以不经修改或很少修改就能与UTF-8一起使用。
      • 使用标准的面向字节的排序例程对UTF-8排序将产生与基于Unicode代码点排序相同的结果。(尽管这只有有限的有用性,因为在任何特定语言或文化下都不太可能有仍可接受的文字排列顺序。
      • UTF-8UTF-16都是可扩展标记语言文档的标准编码。所有其它编码都必须通过显式或文本声明来指定。[2]
      • 任何面向字节字符串搜索算法都可以用于UTF-8的数据(只要输入仅由完整的UTF-8字符组成)。但是,对于包含字符记数的正则表达式或其它结构必须小心。
      • UTF-8字符串可以由一个简单的算法可靠地识别出来。就是,一个字符串在任何其它编码中表现为合法的UTF-8的可能性很低,并随字符串长度增 长而减小。举例说,字元值C0,C1,F5FF从来没有出现。为了更好的可靠性,可以使用正则表达式来统计非法过长和替代值(可以查看W3 FAQ: Multilingual Forms上的验证UTF-8字符串的正则表达式)。
    • 缺点
      • 一份写得很差(并且与当前标准的版本不兼容)的UTF-8解析器可能会接受一些不同的伪UTF-8表示并将它们转换到相同的Unicode输出上。这为设计用于处理八位表示的校验例程提供了一种遗漏信息的方式。

--UTF-8

 

其中还有:

UTF-8编码的缺点

不利于正则表达式检索

正则表达式可以进行很多英文高级的模糊检索。例如,[a-h]表示ah间所有字母。

同样GBK编码的中文也可以这样利用正则表达式,比如在只知道一个字的读音而不知道怎么写的情况下,也可用正则表达式检索,因为GBK编码是按读音排序的。只是UTF-8不是按读音排序的,所以会对正则表达式检索造成不利影响。但是这种使用方式并未考虑中文中的破音字,因此影响不大。Unicode是按部首排序的,因此在只知道一个字的部首而不知道如何发音的情况下,UTF-8 可用正则表达式检索而GBK不行。

其他

与其他 Unicode 编码相比,特别是UTF-16,在 UTF-8 ASCII 字元占用的空间只有一半,可是在一些字元的 UTF-8 编码占用的空间就要多出,特别是中文、日文和韩文(CJK)这样的象形文字,所以具体因素因文档而异,但不论哪种情况,差别都不可能很明显。

 

呵呵,自从可以访问WiKi以后,基本成了一些名字解释等信息的重要来源了。。。。希望以后可以一直访问到。(个人想法)

 

另外,对于服务器来讲,调试信息的主要出口在两个地方,一个是记录日志文件,一个就是控制台输出了,按我们总监的话来说@#@#……¥#算了,不说了,反正总监的大意就是服务器就是看着一屏屏的文字,没有写客户端代码那样有成就感,能够即时的显示出来,但是,别小看这些文字,很多bug调试的时候不一定能出来,偶尔出现一次,基本得靠日志分析,而控制台的输出主要目的可能就是即时的对服务器运行状况有所了解(其实真正后期运行个人感觉并不是太主要),主要的还是以文件形式记录的日志,而且控制台的输出对于效率的影响过大。(其实所有的I/O操作都是相对来说很耗时间的)

       正在世界地图服务器,这里讲一个插曲,我们公司的服务器日志目前全部是英文................呵呵,原因是Linux下的中文输出有问题-_-!当然有问题,公司为了保证游戏客户端的效率,所有的字符处理全部是以UCS-2编码的两字节wchar_t来表示,不经过任何转换能显示出来才怪了-_-!。。。。。

 

首先是控制台的输出,Windows下:

一般来说,用VS2005中用wchar_t来表示UTF16中文就比较合适了,因为目前VS2005wchar_t默认就是两字节的,下面是演示过程的源代码:

// Unicode.cpp : 定义控制台应用程序的入口点。

//

#include <stdio.h>

#include <tchar.h>

#include <windows.h>

#include <locale.h>

#include "ConvertUTF.h"

 

int _tmain(int argc, _TCHAR* argv[])

{

    setlocale(LC_ALL, "");

    wprintf(L"windows控制台在C语言中输出UCS-2宽字节中文-----begin-----/n");

    ConversionResult result = sourceIllegal;

    wchar_t lwcBuf[3] = L"中文";

    UTF16 utf16_buf[3] = {0};

    utf16_buf[0] = 0x4e2d;

    utf16_buf[1] = 0x6587;

    utf16_buf[2] = 0;

    UTF16 *utf16Start = utf16_buf;

    UTF8 utf8_buf[12] = {0};

    UTF8* utf8Start = utf8_buf;

 

    wprintf(L"%s/n", (wchar_t*)utf16_buf);

    wprintf(L"%s/n", lwcBuf);

 

    result = ConvertUTF16toUTF8((const UTF16 **) &utf16Start, &(utf16_buf[3]), &utf8Start, &(utf8_buf[12]), strictConversion);

    switch (result) {

       default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);

       case conversionOK: break;

       case sourceExhausted: printf("sourceExhausted/t"); exit(0);

       case targetExhausted: printf("targetExhausted/t"); exit(0);

       case sourceIllegal: printf("sourceIllegal/t"); exit(0);

    }

 

    printf("is leagal UTF8: %d/n", isLegalUTF8Sequence(utf8_buf, utf8Start));

    printf("%s/n", (char*)utf8_buf);

 

    // 清空缓存,以确定以后的值的确是转换得来

    ZeroMemory(utf16_buf, sizeof(utf16_buf));

 

    // 由于转换中利用了这两个start,所以需要重新为start定位,并且保存住End

    UTF8* utf8End = utf8Start;

    utf8Start = utf8_buf;

    utf16Start = utf16_buf;

 

    result = ConvertUTF8toUTF16((const UTF8 **) &utf8Start, utf8End, &utf16Start, &(utf16_buf[3]), strictConversion);

    switch (result) {

       default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);

       case conversionOK: break;

       case sourceExhausted: printf("sourceExhausted/t"); exit(0);

       case targetExhausted: printf("targetExhausted/t"); exit(0);

       case sourceIllegal: printf("sourceIllegal/t"); exit(0);

    }

 

    wprintf(L"%s/n", (wchar_t*)utf16_buf);

    wprintf(L"windows控制台在C语言中输出UCS-2宽字节中文-----end-----/n");

 

    return 0;

}

 

运行结果:

windows控制台在C语言中输出UCS-2宽字节中文-----begin-----

中文

中文

is leagal UTF8: 1

枃

中文

windows控制台在C语言中输出UCS-2宽字节中文-----end-----

 

还是利用了上一篇提到的函数,需要注意的是在C语言中不使用setlocale函数是没有办法正确输出utf16中文的,(因为默认可能是使用了windows以前的codepage那一套系统)设置后一切非常正常。无论是用short标志的中文,或者是直接输入的中文,控制台都能正确输出,但是utf8显然没有得到MS的支持,一样的,在VS2005中,UTF8字符不能正常的通过鼠标停留显示出来。。。。

以上是C语言的方案,C++的输出方式如下:(从原来的代码改过来的,有点乱,但是仅仅作为测试,没有考虑那么多了)

 

#include <stdio.h>

#include <tchar.h>

#include <windows.h>

#include <locale.h>

#include <iostream>

#include "ConvertUTF.h"

using namespace std;

 

void TestInCPP()

{

    // 未为wcout流设置locale前的效果,cout能够输出中文是因为Windows以前的多字节技术

    cout <<"未为wcout流设置locale前的效果: /"";

    wcout <<L"中文";

    cout <<"/"" <<endl;

 

    // 可以看到什么效果都没有,还会将wcout设置错误标志位,不清楚此标志位将什么输出都没有

    wcout.clear();

 

    wcout.imbue(locale(""));

    wcout <<L"windows控制台在C++语言中输出UCS-2宽字节中文-----begin-----/n" <<endl;

    ConversionResult result = sourceIllegal;

    wchar_t lwcBuf[3] = L"中文";

    UTF16 utf16_buf[3] = {0};

    utf16_buf[0] = 0x4e2d;

    utf16_buf[1] = 0x6587;

    utf16_buf[2] = 0;

    UTF16 *utf16Start = utf16_buf;

    UTF8 utf8_buf[12] = {0};

    UTF8* utf8Start = utf8_buf;

 

    wcout<<L"utf16_buf:" <<(wchar_t*)utf16_buf <<L" lwcBuf:" <<lwcBuf <<endl;

 

    result = ConvertUTF16toUTF8((const UTF16 **) &utf16Start, &(utf16_buf[3]), &utf8Start, &(utf8_buf[12]), strictConversion);

    switch (result) {

       default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);

       case conversionOK: break;

       case sourceExhausted: printf("sourceExhausted/t"); exit(0);

       case targetExhausted: printf("targetExhausted/t"); exit(0);

       case sourceIllegal: printf("sourceIllegal/t"); exit(0);

    }

 

    wcout <<L"Is leagal UTF8:" <<isLegalUTF8Sequence(utf8_buf, utf8Start) <<endl;

    cout.imbue(locale(""));

    cout <<(char*)utf8_buf <<endl;

 

    // 清空缓存,以确定以后的值的确是转换得来

    ZeroMemory(utf16_buf, sizeof(utf16_buf));

 

    // 由于转换中利用了这两个start,所以需要重新为start定位,并且保存住End

    UTF8* utf8End = utf8Start;

    utf8Start = utf8_buf;

    utf16Start = utf16_buf;

 

    result = ConvertUTF8toUTF16((const UTF8 **) &utf8Start, utf8End, &utf16Start, &(utf16_buf[3]), strictConversion);

    switch (result) {

       default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);

       case conversionOK: break;

       case sourceExhausted: printf("sourceExhausted/t"); exit(0);

       case targetExhausted: printf("targetExhausted/t"); exit(0);

       case sourceIllegal: printf("sourceIllegal/t"); exit(0);

    }

 

    wcout<<L"utf16_buf:" <<(wchar_t*)utf16_buf <<endl;

 

    wcout <<L"windows控制台在C++语言中输出UCS-2宽字节中文-----end-----/n" <<endl;

}

 

int _tmain(int argc, _TCHAR* argv[])

{

 

    TestInCPP();

 

    return 0;

}

 

可以看到,C++的使用方法更加有效一些,因为仅仅影响到某个特定的输出流,而不是对所有的东西都一次性更改,这点与C++中面向对象的思想相符合,C语言那种方式太霸道了一点(符合C语言全局设定的风格)。但是作为操作系统的特性,C++也没有办法超越,去将UTF-8编码的字符串输出。

所以,最后的结论是,要在windows的控制台输出UTF-8编码的字符串似乎唯一的办法就是先转换成UCS-2然后再输出了。-_-!

这里要提及的就是同时使用两种机制会有冲突,记得当时看到有文章说,CC++的这两种机制最好不要混用-_-!

  

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

UCS-2与UTF8之间的选择(2)--Unicode组织提供的C/C++的Unicode编码转换函数


UCS-2UTF8之间的选择(2--Unicode组织提供的C/C++Unicode编码转换函数

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

这里我大概搜索到了3个库,一个是Unicode组织自己提供的,一个是IBM做的开源ICU工程中的,一个是开源的UCData 2.9

但是UCData2.9中并没有UTF-16,通篇都是为UTF-32设计的,属于那种将来可能会用的到的库,因为目前WindowsLinux的内核都还没有UTF-32化,注定假如使用UTF-32将会在两个地方都失去性能优势。

于是剩下的两个选择就很有意思了,一个是Unicode自己提供的那几个简单的转换函数,一个是IBM提供的一个巨无霸的库,一个Unicode的相关库到了10M的确算是很夸张的了,难怪被别人称作蓝色巨人,干什么都是大手笔,就像它出品的软件工程软件一样,哪一个都是出手不凡,一出一个系列,每个都大的要命。感叹。。。。。

 

C/C++的库的角度,我就只查看这两个了。

Unicode组织的函数:(甚至不能称作库)

这些函数很好用,并且自带了测试套件。

windows中,新建一个空的工程,并将ConvertUTF.h,CVTUTF7.h,CVTUTF7.C,harness.c添加到工程中,编译,运行,既可以运行测试例程,utf7这个我们一般用不上,其实也可以不添加。在我的VS2005 SP1中,运行结果如下:

Three tests of round-trip conversions will be performed.

One test of illegal UTF-32 will be peroformed.

Two illegal result messages are expected; one in test 02A; one in test 03A.

These are for tests of Surrogate conversion.

 

Begin Test01

******** Test01 succeeded without error. ********

 

Begin Test02

Test02A for 55296, input 0000d800, output 0000,0000, result 3

!!! Test02A: note expected illegal result for 0x0000D800

******** Test02 succeeded without error. ********

 

Begin Test03

sourceIllegal   Test03A for 55296 (0xd800); output ; result 3

!!! Test03A: note expected illegal result for 0x0000D800

******** Test03 succeeded without error. ********

 

Begin Test04

******** Test04 succeeded without error. ********

 

很显然的4个测试全部通过,没有任何错误。

提供的我们可能需要的函数有:

isLegalUTF8Sequence()判断一个字符串是否是合法的UTF8字符串

ConvertUTF8toUTF16()转换UTF8字符串到UTF16

ConvertUTF16toUTF8()转换UTF16字符串到UTF8

原型:

ConversionResult ConvertUTF8toUTF16 (

       const UTF8** sourceStart, const UTF8* sourceEnd,

       UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags);

 

ConversionResult ConvertUTF16toUTF8 (

       const UTF16** sourceStart, const UTF16* sourceEnd,

       UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags);

所有的函数采取的结尾判断方式是传递一个结束的指针位置,而不是常见的长度。

 

其提供的几个宏也很有用:

typedef unsigned long    UTF32; /* at least 32 bits */

typedef unsigned short   UTF16; /* at least 16 bits */

typedef unsigned char    UTF8;  /* typically 8 bits */

typedef unsigned char    Boolean; /* 0 or 1 */

 

BooleanC++中似乎是不怎么需要.

 

/* Some fundamental constants */

#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD

#define UNI_MAX_BMP (UTF32)0x0000FFFF

#define UNI_MAX_UTF16 (UTF32)0x0010FFFF

#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF

#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF

 

很有价值的几个宏,除了都强制转换为UTF32比较郁闷,UNI_MAX_BMPUNI_MAX_UTF16可以用的上。

 

typedef enum {

    conversionOK,        /* conversion successful */

    sourceExhausted,  /* partial character in source, but hit end */

    targetExhausted,  /* insuff. room in target for conversion */

    sourceIllegal     /* source sequence is illegal/malformed */

} ConversionResult;

 

这是上述那几个函数的返回值,英文注释解释的很清楚了。

 

typedef enum {

    strictConversion = 0,

    lenientConversion

} ConversionFlags;

 

这是那几个函数的第五参数,一个表示严格转换,一个表示宽容的转换

 

排除harness.c,不编译,添加ConvertUTF.c,自己制作一个测试:

 

// Unicode.cpp : 定义控制台应用程序的入口点。

//

#include <stdio.h>

#include <tchar.h>

#include <windows.h>

#include "ConvertUTF.h"

 

int _tmain(int argc, _TCHAR* argv[])

{

    ConversionResult result = sourceIllegal;

    UTF16 utf16_buf[8] = {0};

    utf16_buf[0] = 0xd834;

    utf16_buf[1] = 0xdf00;

    utf16_buf[2] = 0xd834;

    utf16_buf[3] = 0xdf01;

    utf16_buf[4] = 0xd834;

    utf16_buf[5] = 0xdf02;

    utf16_buf[6] = 0;

    utf16_buf[7] = 0;

    UTF16 *utf16Start = utf16_buf;

    UTF8 utf8_buf[16] = {0};

    UTF8* utf8Start = utf8_buf;

 

    MessageBoxW(NULL, (LPWSTR)utf16_buf, L"Before trans", MB_OK);

    result = ConvertUTF16toUTF8((const UTF16 **) &utf16Start, &(utf16_buf[6]), &utf8Start, &(utf8_buf[16]), strictConversion);

    switch (result) {

       default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);

       case conversionOK: break;

       case sourceExhausted: printf("sourceExhausted/t"); exit(0);

       case targetExhausted: printf("targetExhausted/t"); exit(0);

       case sourceIllegal: printf("sourceIllegal/t"); exit(0);

    }

 

    // 清空缓存,以确定以后的值的确是转换得来

    ZeroMemory(utf16_buf, sizeof(utf16_buf));

 

    // 由于转换中利用了这两个start,所以需要重新为start定位,并且保存住End

    UTF8* utf8End = utf8Start;

    utf8Start = utf8_buf;

    utf16Start = utf16_buf;

 

    result = ConvertUTF8toUTF16((const UTF8 **) &utf8Start, utf8End, &utf16Start, &(utf16_buf[6]), strictConversion);

    switch (result) {

       default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);

       case conversionOK: break;

       case sourceExhausted: printf("sourceExhausted/t"); exit(0);

       case targetExhausted: printf("targetExhausted/t"); exit(0);

       case sourceIllegal: printf("sourceIllegal/t"); exit(0);

    }

 

    MessageBoxW(NULL, (LPWSTR)utf16_buf, L"After Trans", MB_OK);

 

    return 0;

}

 

 

利用的还是以前的太玄经字符,经测试,两次的MessageBox显示均正常,唯一需要注意的是由于转换函数的第13个参数都是以指针的指针为参数,并且跟进实现中可以发现,实现中利用了此指针的偏转来完成长度的判断,并且利用此值作为结束的返回,好处自然是还是以指针作为结束的返回了,坏处就是你需要重新利用的时候需要重新定位。如上述源代码及中间的注释所示,这可能也就是这些函数唯一需要注意的地方了,因为和平时对长度的判断不同。

至于为什么要这样做,可能是出于效率的考虑,就像C语言中以NULL为字符串的结束标志而不是在字符串前加一个长度一样,虽然此点因为容易导致严重的越界问题而被人诟病,但是实际上,这样比记长度能够更加有效率的处理字符串是很多人没有发现的。

当以某个值为结束标志(比如C 语言中的NULL)时,遍历只需要偏移起始指针,并在循环每次做一个判断,大概如下形式,这样每次循环只需要一次的比较,一次的++

for(; p != NULL; ++p)

{

    // 此处可以直接处理*p

}

 

假如是长度呢?

可能需要如下形式,一方面需要额外的一个临时变量,一方面对字符串中值的调用效率也更加低了。而这些消耗在以’/0’结尾时就不存在。

for(int i = 0; i < END; ++i)

{

       // p[i]或者p+i的处理方式

}

 

这点也很好解释,为什么到了C++中迭代器还是喜欢用类似的形式:)

for(lit = container.begin(); lit != container.end(); ++lit)

{

 // 直接使用 *lit

}

 

这在效率至上的世界中,很好理解。

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

服务器Select模型的实现


服务器Select模型的实现

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

select模型属于网络的I/O复用模型,比纯粹的阻塞I/O模型更具有实用性,因为可以同时等待多个描述字的就绪。

当年学习C/C++的时候,很少碰到底层以数字标示的描述字,只在写文件系统的去尝试各种情况,以获得最佳效率的时候实际尝试使用过一次,一直觉得那种open,write,read的文件操作方式,实在是比fopen一族函数还要低级的方式-_-!平时没有必要使用。但是等到网络编程的时候,才发现。。。。原来这么底层的东西,竟然也有一定的通用性,文件的描述字和网络的描述字竟然是一致的-_-!不管是谁设计的,还是挺佩服的。。。。。。

       这里仅仅是为了学习Select模型而写的学习例子,作用是在服务器端输出连接上的客户端的IP(仅以数字形式),然后将客户端的IP以字符串的形式返回,客户端连接服务器,并接受由服务器端返回的IP地址,然后输出转换为字符串形式的IP地址和数字形式的IP地址,为了区别select到正确的不同listen套接字,这里用了不同的端口,并且不同的两个套接字响应时以echo 1,echo 2区别。功能是很简单的,仅仅用于学习,所以其中很多地方本来可以抽出来称为函数的,都贪简单,直接复制了(-_-!这里本来习惯想说Ctrl-C Ctrl-V的。。。但是发现自己实在Ubuntu下用vim复制的,好像和实际情况不符。。。。)

       另外。。。。由于用的是《Unix Network Programming》一书,所以编程风格都变得有点像书中了。。。。服务器端全是自己写的,客户端代码由书中的daytime客户端改过来的,并且发现书中客户端代码都不关闭套接字,都交由退出进程的时候由系统关闭,不知道这种风格好不好。由于学习。。。写的是ANSI C程序,用gcc编译-_-!

unp.h是《Unix Network Programming》源代码中的公用头文件,makefile可能也得注意一下,为了图省事,我用了其源代码中的Make.defines,因为这样比自己写简单多了:),makefile就不贴了,没有什么学习意义。

 

运行效果如下:

客户端运行:

./TestSelectCli 127.0.0.1 1000

Conncet OK

127.0.0.1:16777343 Echo 1.

laptop:~/unpv1/unpv13e/MyTest$ ./TestSelectCli 127.0.0.1 1001

Conncet OK

127.0.0.1:16777343 Echo 2.

laptop:~/unpv1/unpv13e/MyTest$ ./TestSelectCli 192.168.0.138 1000

Conncet OK

192.168.0.138:2315299008 Echo 1.

laptop:~/unpv1/unpv13e/MyTest$ ./TestSelectCli 192.168.0.138 1001

Conncet OK

192.168.0.138:2315299008 Echo 2.

 

服务器端输出:

2315299008 Echo 1.

16777343 Echo 1.

16777343 Echo 2.

2315299008 Echo 1.

2315299008 Echo 2.

 

 

服务器端源代码:

 

  1 #include    "unp.h"
  2
  3
  4 void str_echo1(int connfd);
  5 void str_echo2(int connfd);
  6
  7 int main(int argc, char **argv)
  8 {
  9     struct sockaddr_in cliaddr;
 10     pid_t childpid;
 11
 12     /*  Bind 1000 port to listen socket 1 */
 13     int listenfd1 = Socket(AF_INET, SOCK_STREAM, 0);
 14
 15     struct sockaddr_in servaddr;
 16     bzero(&servaddr, sizeof(servaddr));
 17     servaddr.sin_family = AF_INET;
 18     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 19     servaddr.sin_port = htons(1000);
 20
 21     Bind(listenfd1, (SA *)&servaddr, sizeof(servaddr));
 22
 23     Listen(listenfd1, LISTENQ);
 24
 25     /*  Bind 1001 port to listen socket 2*/
 26     int listenfd2 = Socket(AF_INET, SOCK_STREAM, 0);
 27
 28     struct sockaddr_in servaddr2;
 29     bzero(&servaddr2, sizeof(servaddr2));
 30     servaddr2.sin_family = AF_INET;
 31     servaddr2.sin_addr.s_addr = htonl(INADDR_ANY);
 32     servaddr2.sin_port = htons(1001);
 33
 34     Bind(listenfd2, (SA *)&servaddr2, sizeof(servaddr2));
 35
 36     Listen(listenfd2, LISTENQ);
 37
 38     /* Initialize fd_set struct */
 39     int maxfdp1 = max(listenfd1, listenfd2) + 1;
 40     fd_set rset;
 41     FD_ZERO(&rset);
 42
 43     /*  Select from this two listen socket */
 44     for( ; ; )
 45     {
 46         FD_SET(listenfd1, &rset);
 47         FD_SET(listenfd2, &rset);
 48
 49         int nready = -1;
 50         if( (nready = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0)
 51         {
 52             if(EINTR == errno)
 53             {
 54                 continue;
 55             }
 56             else
 57             {
 58                 err_sys("Select error.");
 59             }
 60         }
 61
 62         /*  some one listening socket is readable.*/
 63         if(FD_ISSET(listenfd1, &rset))
 64         {
 65             socklen_t len = sizeof(cliaddr);
 66             int connfd = Accept(listenfd1, (SA *)&cliaddr, &len);
 67
 68             if( 0 == (childpid = Fork()) )
 69             {
 70                 /* child process */
 71                 Close(listenfd1);
 72
 73                 str_echo1(connfd);
 74                 exit(0);
 75             }
 76
 77             /* parent process  */
 78             Close(connfd);
 79
 80         }
 81
 82
 83         if(FD_ISSET(listenfd2, &rset))
 84         {
 85             socklen_t len = sizeof(cliaddr);
 86             int connfd = Accept(listenfd2, (SA *)&cliaddr, &len);
 87
 88             if( 0 == (childpid = Fork()) )
 89             {
 90                 /* child process */
 91                 Close(listenfd2);
 92
 93                 str_echo2(connfd);
 94                 exit(0);
 95             }
 96
 97             /* parent process  */
 98             Close(connfd);
 99
100         }
101
102     }
103
104     exit(0);
105 }
106
107 void str_echo1(int connfd)
108 {
109     struct sockaddr_in clientAddr;
110     socklen_t len = sizeof(clientAddr);
111
112     if(getpeername(connfd, (SA*) &clientAddr, &len) < 0)
113     {
114         return;
115     }
116
117     char lcBuffer[MAXLINE] = {0};
118     sprintf(lcBuffer, "%u Echo 1.", clientAddr.sin_addr.s_addr);
119
120     printf("%s/n", lcBuffer);
121
122     Write(connfd, lcBuffer, MAXLINE);
123 }
124
125
126 void str_echo2(int connfd)
127 {
128     struct sockaddr_in clientAddr;
129     socklen_t len = sizeof(clientAddr);
130
131     if(getpeername(connfd, (SA*) &clientAddr, &len) < 0)
132     {
133         return;
134     }
135
136
137     char lcBuffer[MAXLINE] = {0};
138     sprintf(lcBuffer, "%u Echo 2.", clientAddr.sin_addr.s_addr);
139
140     printf("%s/n", lcBuffer);
141
142     Write(connfd, lcBuffer, MAXLINE);
143 }
144
145

 

客户端源代码:

 1 #include    "unp.h"
 2
 3 int main(int argc, char **argv)
 4 {
 5     int                    sockfd, n;
 6     char               recvline[MAXLINE + 1];
 7     struct sockaddr_in servaddr;
 8
 9     if (argc != 3)
10         err_quit("usage: a.out <IPaddress> <IPPort>");
11
12     int port = atoi(argv[2]);
13
14     if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
15         err_sys("socket error");
16
17     bzero(&servaddr, sizeof(servaddr));
18     servaddr.sin_family = AF_INET;
19     servaddr.sin_port   = htons(port);
20     if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
21         err_quit("inet_pton error for %s", argv[1]);
22
23     if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
24         err_sys("connect error");
25
26     printf("Conncet OK/n");
27
28     while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
29         recvline[n] = 0;  /* null terminate */
30
31         /*  change number string to number and to ip string */
32         struct in_addr svraddr;
33         svraddr.s_addr = strtoul(recvline, NULL, 10);
34         char *pszsvraddr = inet_ntoa(svraddr);
35
36         printf("%s:%s/n", pszsvraddr, recvline);
37     }
38     if (n < 0)
39         err_sys("read error");
40
41     exit(0);
42 }

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

确定Windows XP到底是UCS-2的还是UTF-16的


确定Windows XP到底是UCS-2的还是UTF-16

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

 

一般认为Windows下以16bit表示的Unicode并不是UTF-16,而是UCS-2UCS-2是一种编码格式,同时也是指以一一对应关系的Unicode实现。在UCS-2中只能表示U+0000U+FFFFBMP(Basic Multilingual Plane ) Unicode编码范围,属于定长的Unicode实现,而UTF-16是变长的,类似于UTF-8的实现,但是由于其字节长度的增加,所以BMP部分也做到了一一对应,但是其通过两个双字节的组合可以做到表示全部Unicode,表示范围从U+0000 U+10FFFF。关于这一点,我在很多地方都看到混淆了,混的我自己都有点不太肯定自己的说法了,还好在UTF-16/UCS-2中还是区别开了,不然我不知道从哪里去寻找一个正确答案。(哪怕在IBM的相关网页上都将UCS-2作为UTF-16的别名列出)

UTF-16/UCS-2文中有以下内容:

UTF-16 is the native internal representation of text in the Microsoft Windows 2000/XP/2003/Vista/CE; Qualcomm BREW operating systems; the Java and .NET bytecode environments; Mac OS X's Cocoa and Core Foundation frameworks; and the Qt cross-platform graphical widget toolkit.[1][2][citation needed]

Symbian OS used in Nokia S60 handsets and Sony Ericsson UIQ handsets uses UCS-2.

The Joliet file system, used in CD-ROM media, encodes filenames using UCS-2BE (up to 64 Unicode characters per file).

Older Windows NT systems (prior to Windows 2000) only support UCS-2.[3]. In Windows XP, no code point above U+FFFF is included in any font delivered with Windows for European languages, possibly with Chinese Windows versions.[clarification needed]

 

很明确的说明了Windows 2000以后内核已经是UTF-16的了,这点还真是与平时的感觉相违背,于是可以测试一下。在UTF-16的编码转换函数(Python实现)

中我在windows下输出了三个太玄经的字符,“

阅读全文....

windows/linux服务器程序支持库的开发(1)--UCS-2与UTF8之间的选择(1)


windows/linux服务器程序支持库的开发(1--UCS-2UTF8之间的选择(1

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

总体计划见:

 工作之外的学习/开发计划(1) -- windows/linux服务器程序支持库的开发

 

此为序篇。

 

首先纠正几个名词

作为常常在windows下开发的人员,一般将windows下的Unicode实现直接称为Unicode,(比如我在前面就一直是这样做的),而把其他的Unicode实现称为更加具体的名称,比如UTF-8,UTF-16等,但是既然这里要讨论各种Unicode的实现,所以,还是得好好的区分一下,这里,将windows下的Unicode实现正名为UTF-16,但是遗憾的是,我发现Windows下的字符串处理函数实际仅仅是支持UCS-2(Window XP下测试结果),所以从编程的角度来讲,我们还是只能使用UCS-2,见文章《确定Windows XP到底是UCS-2的还是UTF-16》,所以,这里都不混淆的情况下都以UCS-2代替。

       以一个字符对应一个Unicode编码,并且要表达全部的Unicode编码,连普通的两个字节的wchar_t都不够,所以,现在有了新的UTF-32,gccwchar_t也默认已经是32位的了,顺应的就是这种潮流,毕竟,在现在,硬盘内存都当白菜卖了,相对而言,不在乎那么点空间,最最重要的是编程方便,还有速度。

关于此部分内容参看UTF-16/UCS-2

关于Unicode的编码范围的内容参看Mapping of Unicode character planes

 

名字缩写注释:

通用字符集 (Universal Character Set, UCS)

统一码转换格式(Unicode Transformation FormatUTF

 

 

其次,讲讲Unicode

假如初次接触,那么要了解清楚Unicode还不是那么简单的事情,这是实话。当年语言编码混乱的时代,一种语言的软件不能正常的在另外语言的操作系统上显示,编多语言的程序是件非常痛苦的事情,编写一个可以在不同操作系统上都显示正确字符的软件那是高难度的事情。当时呼吁统一编码的呼声很高,原以为Unicode一出,将来的程序员将是无比幸福,甚至都不会了解什么叫编码,什么叫代码页(Codepage),因为已经不用考虑了。

但是,事实是残酷的,当Unicode比较普及了以后,由于历史的原因,光是Unicode本身的实现的种类过多,导致的各个操作系统不兼容的问题,都能够让程序员苦恼痛苦不已,比如,现在要做的UTF-8UCS-2之间的选择。可能无论哪个初学windows的人都会迷惑于其A,W的函数结尾,初学MFC编程的人都会为其中大量的_T()使用感到莫名奇妙,这些,其实都是历史原因,为了当时一代程序员的理想,用一套源码,实现ASCIIUCS-2两套编码兼容。

由于Windows的内核都已经是UTF-16化了,所以实际的开发中,即使不用到除英语以外的语言,使用Unicode其实都是有益的,起码对于现在的速度比内存更重要的时候,一般是有益的,因为使用UCS-2最多是字符多占用了一倍的内存,而使用ASCII实际是影响了所有字符串有关的API函数调用的速度。对于现实中几乎完全不可能去使用ASCII编译程序的中国程序员,还是按照老美们为了他们的习惯去规定的_T()模式,至今,我还感叹不已,毕竟,计算机的教育水平,国外和国内的话语权不是一个量级的,个人也是几乎看着“洋书”成长的,啃过英文原版技术书籍,而且哪怕是中文版的MSDN其实也是英文居多,所以,我们竟然会有这样符合老美们特殊国情的编程习惯-_-!

       闲话少说,关于Unicode的起源发展,我在一篇《UTF-8UnicodeFAQ》文档中见到过。这里是其英文版《UTF-8 and Unicode FAQ for Unix/Linux》。

Unicode组织的官方主页上 http://unicode.org/ 的顶头,有这么一段话:

Welcome! The Unicode Consortium enables people around the world to use computers in any language. Our members develop the Unicode Standard, Unicode Locales (CLDR), and other standards. These specifications form the foundation for software internationalization in all major operating systems, search engines, applications, and the Web.

他们一点也不夸张,Unicode正是所有主要操作系统,搜索引擎,应用程序和网页的软件国际化的基础。这里我突然想起以前给lua for windows提交的那个库中的bug时,lua for windows的作者竟然回了两个中文字“关心”给我^^(虽然我不知道什么意思),见《真高兴啊。。。。实际的为开源事业做了点点贡献:),很久前指出的一个lua stdlibbug得到确认》这在以前是不可想象的,因为在以前,英文的操作系统中要输出并显示个中文谈何容易啊。

这里我引用MS的话来说明一下Unicode的威力:

 “采用支持 Unicode 的单源代码库使开发时间得以缩短,Unicode Microsoft 带来的好处是显而易见的。就 Windows® 2000 来说,在发布英文产品后需要花费几个月的时间来准备其本地化版本。而对于 Windows XP,这一周期已缩短为几周时间。”—MS通过 Unicode 5.0 将您的应用程序应用到全球

这就是Unicode的威力,为程序员节省的时间是从几个月到几周这个量级。

 

候选的UTF-8/UCS-2

现在开发软件用Unicode,当然是没有疑问了。在windows下面直接用UCS-2估计也没有疑问。但是考虑的可移植的时候,问题就来了,目前Linux的内核Unicode实现都是UTF-8的,并且gcc新版的wchar_t已经是新标准的4字节了,这样就算同样的宽字符接口,可能也不一定和windowswchar_t兼容,要用同一套代码可能还得设置-fshort-wchar的编译选项,看看这样的mannul说明,就知道这样做不是什么长久之计(我们公司一直这样做):

-fshort-wchar

Override the underlying type for wchar_t to be short unsigned int           instead of the default for the target.  This option is useful for building programs to run under WINE.

Warning: the -fshort-wchar switch causes GCC to generate code that is not inary compatible with code generated without that switch. Use it to conform to a non-default application binary interface.

这样个人感觉也不是什么长久之记,其实还不如用UTF8方便,用UTF8的坏处很明显,那就是处理的函数少一些,而且,字符的长度不一,不如UNICODE好处理。

这里真是个两难的选择,我需要好好研究研究,以后暂时遵循这个规则,之所以说暂时,原因很简单,我个人认为,将来UTF-32才是主流的编码方式,并且UTF-32也是以后唯一需要的编码方式。

 

首先,从理论上分析各自的优点缺点:

UTF8:

优点:

1.UCS 字符 U+0000 U+007F (ASCII) 被编码为字节 0x00 0x7F (ASCII 兼容) 这意味着只包含 7 ASCII 字符的文件在 ASCII UTF-8 两种编码方式下是一样的。意味着完美兼容ASCII--UTF-8 and Unicode FAQ

2.Linux的内核是UTF88的,意味着在Linux下使用UTF8有得天独厚的优势。而且不仅仅是Linux,大量的开源计划(由于大部分开源计划都是围绕着Linux走的),包括网页,XML等也都是原生UTF-8的,可参考源程序范例更多。

3.由于其单字节编码的方式,不用考虑大头小头的字节序问题。

4.以英文为主的数据在存储时,占用的空间较小。

 

缺点:

1.  大量的组合字符(即用两个以上字节来表示一个字符),使得字符串的处理很不方便。有着和任何变长编码一样的痛苦。并且因为这个原因,当字符超出ASCII范围时,原有的C/C++字符串函数运行结果都不会是你想要的。

2.  以非ASCII字符为主的数据在存储时,占用空间较大。

 

 

UCS-2

优点:

1.统一定长编码,都是两个字节对应一个字符,所以对于字符串的处理非常方便。没有变长的痛苦。大量的宽字节C/C++函数都可以直接使用。

2.Windows的内核是UTF-16的,但是由于UCS-2仅仅是UTF-16的一个子集,所以在Windows下使用UCS-2,有得天独厚的优势。另外,从资料上看,使用UTF-16/UCS-2的操作系统阵营比使用UTF-8的更加庞大。见《UTF-16/UCS-2

》的“Use in major operating systems and environments”一节,带来的好处是,在与不同语言/平台的程序(毕竟我要写的是网络程序)进行通信时,会更加方便。

 

缺点:

1. Unix 下使用 UCS-2 ( UCS-4) 会导致非常严重的问题. 用这些编码的字符串会包含一些特殊的字符, 比如 '/0' '/', 它们在 文件名和其他 C 库函数参数里都有特别的含义. 另外, 大多数使用 ASCII 文件的 UNIX 下的工具, 如果不进行重大修改是无法读取 16 位的字符的. 基于这些原因, 在文件名, 文本文件, 环境变量等地方, UCS-2 不适合作为 Unicode 的外部编码. --UTF-8 and Unicode FAQ》,实际其意思就是原有的C语言字符串函数将完全失效,哪怕你用的就是ASCII字符也是这样。

 

计划:

上述的比较仅仅是理论上的,并且似乎难决高下,以下将具体的分别在LinuxWindows不同的环境下尝试使用UCS-2UTF-8,以做出决定。

大概会尝试的有:

C/C++ 中使用,Python中使用,Lua中使用,正则表达式的使用。

 

题外话:

之所以花这么多时间来研究这个问题,出于很多原因,有人说,编程的主要时间是花在进行字符串的处理上了,而现在这样的选择,将会决定以后我大部分时间是否是会更长,还是更短,是更容易,还是更难的问题。而且,对程序的效率,空间占用,肯定也是有影响的。对于初学者来说,一般用ANSI学习就完了,到了真实的使用,这些问题的考虑,甚至比可以想象的还要重要。

对于这个话题,著名开源计划Samba发起人Andrew Tridgell,曾经在为Samba适应WindowsUCS-2时,提出了一个移植计划,一步一步的将Samba的内核从UTF-8移植到UCS-2.  utf8 vs ucs2》,颇有味道:)对于同样致力于跨平台程序编写的我,也是指导意义很多。毕竟作为一个不准备在Windows下运行,而是仅仅需要与Windows进行通行的一个程序,最后都需要将内核全面的UCS-2化,而我的程序是不仅仅是需要与Windows下程序通信,并且还需要能够在Windows下运行的,是不是更应该用UCS-2呢?有待研究。另外,我还找到了一个在UNIX/Linux下使用UCS-2的范例:)开源就是好啊。

而我公司使用的是UCS-2,主要是因为客户端是肯定跑在Windows下而且是肯定不需要跑到Linux下的,而作为网络游戏,自然以客户端的效率为主,所以服务器多了一下无谓的字符转换操作也接受了。

UCS-2还是UTF-8,需要继续研究...................................

 

 

参考:

UTF-8, UTF-16, UTF-32 & BOM

UTF-16/UCS-2

Unicode详解想当棒的中文讲解

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

UTF-16的编码转换函数(Python实现)

 

UTF-16的编码转换函数(Python实现)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

 

此函数用于将Unicode的编码用UTF-16编码方式表示出来,由于Unicode超过0xFFFF的编码需要用两个16bit联合来表示,所以看起来就没有0xFFFF以下那么一一对应看起来直观。为了使用方便,实现此函数。

其函数实现算法来自UTF-16/UCS-2

具体算法描述如下:

Example UTF-16 encoding procedure

The character at code point U+64321 (hexadecimal) is to be encoded in UTF-16. Since it is above U+FFFF, it must be encoded with a surrogate pair, as follows:

v  = 0x64321
v′ = v - 0x10000
   = 0x54321
   = 0101 0100 0011 0010 0001
 
vh = 0101010000 // higher 10 bits of v′
vl = 1100100001 // lower  10 bits of v′
w1 = 0xD800 // the resulting 1st word is initialized with the high bits
w2 = 0xDC00 // the resulting 2nd word is initialized with the low bits
 
w1 = w1 | vh
   = 1101 1000 0000 0000 |
            01 0101 0000
   = 1101 1001 0101 0000
   = 0xD950
 
w2 = w2 | vl
   = 1101 1100 0000 0000 |
            11 0010 0001
   = 1101 1111 0010 0001
   = 0xDF21

The correct UTF-16 encoding for this character is thus the following word sequence:

0xD950 0xDF21

 

Python实现如下:

def EncodeUTF16(u):

    vc = u - 0x10000

    vh = (vc & 0xFFC00) >>10

    vl = vc & 0x3FF

    w1 = 0xD800

    w2 = 0xDC00

 

    w1 = w1 | vh

    w2 = w2 | vl

 

    return w1,w2

 

强大的Eclipse是我见过的第二个可以直接复制过来就有语法高亮和保持格式的编辑器/IDE,以前我见过的唯一一个就是MS它自己的VS

用此算法计算《U1D300--Tai Xuan Jing(太玄经)Unicode编码.pdf中前三个字符的数值,获得结果:

0x1D300

0xd834 0xdf00

0x1D301

0xd834 0xdf01

0x1D302

0xd834 0xdf02

然后因为X86的机器是小头机,用UltraEdit16进制编辑模式,输入

00000002h: 34 D8 00 DF 34 D8 01 DF 34 D8 02 DF

UTF-16模式保存后,前面加上了BOM,FF FE用于表示小头机,再换回文本模式你就能看到

阅读全文....

MSDN:为了防止数据丢失,请关闭文件后再删除它们


MSDN:为了防止数据丢失,请关闭文件后再删除它们

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

 

今天看ext lib的作者网页上写的,觉得挺有意思,大家也可以去看看:
http://mx-3.cz/tringi/www/langen.php?id=msdn&submenu=3

最绝的一条是:

DeleteFile

...
To prevent loss of data, close files before attempting to delete them.
...

为了防止数据丢失......请关闭文件后再删除它们-_-!

不过我的VS2005版本的MSDN已经没有这个了,不知道作者的版本是多少。

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....