2007年01月10日

网第排名挑战Alexa权威
更新日期:2007-01-10

来源:世界网络 作者:全风

网站排名一直以来就是Alexa的“专利”,然而这种局面正悄悄地被国内的一个网第排名改变。根据网第网站(www.wondi.cn)公布的信息,网第定位在全球中文网站排名,将服务于中国以及所有华文网站。

通过查询第三方提供的网站流量数据可以了解一个网站的运行情况,这对于网站经营者、广告商、投资机构都是一个不可缺少的重要指标。由于过去一直只有Alexa提供所有网站的排名分析数据,Alexa排名成了所有网站经营者的一个答卷评价,这对于大多数中文网站的经营者并不公平。这主要有以下原因:

一是Alexa对中国的网民不够亲和。Alexa排名数据所依赖的Alexa工具条没有中文版本,这对于Alexa工具条在中国的应用推广有很大的限制。Alexa工具条安装率在普通中文上网用户中安装率低。

二是Alexa提供的排名数据有偏差。由于Alexa工具条功能单一,中文网站用户中安装Alexa工具条的大部分限于网站排名的需要,而最广大的网络用户的数据并没有得到公平对待。在Alexa排名,一个礼品购物网站和一个IT网站的Alexa排名不具备可比性。

三是Alexa作弊严重。在数据识别、数据抽取、异常数据等处理过程中,Alexa没有一套应对网站作弊行之有效的方法。只要交付一定的费用,“排名专家”就可以轻松地决定网站的排名。

克服以上问题,提供公平、公正的网站排名及分析数据,也同样是网第排名必须面对的问题。网第网站公布,网第排名的数据基础是在联合了中国诸多门户网站、行业网站、个人网站的基础上,其统计方法和Alexa有些类似。从网第网站查询的数据结果来看,其访问数据分析已经涵括了2006年6月份以来的访问分析数据,给出的排名、访问量、浏览量等网站运行指标和子网站运行指标图文并茂。在整体界面表达和使用方式上,比Alexa更加贴身于中国网民使用。

作为中国本土的新生代网站排名,如若真能克服用户数偏低、行业及地区差异、网站作弊等Alexa排名存在的问题,并能在中国互联网环境中保持其特有的公平、公正性,将在成为中国下一个互联网行业的标准。

2006年11月13日

1 背景
1.1 作者URL
http://spaces.msn.com/members/sharklee/?partqs=cat%3D%25e8%25ae%25a1%25e7%25ae%2597%25e6%259c%25ba%25e4%25b8%258e+Internet&_c11_blogpart_blogpart=blogview&_c=blogpart

2 gsoap文档翻译计划
2.1 gsoap文档翻译计划(序)
 
前一阵子需要在unix c平台下创建一个webservice应用。头痛之余,上网查询相关资料,偶然发现了gsoap开发包。于是乎立即下载试用了一下,效果很好。在它的帮助下,我很快完成了webservice应用的设计开发工作。

今天,心里突然升起一个念头:为何不把整个gsoap的帮助文档翻译一下呢?一来可以方便与其他同仁的交流;二来也可以更好的理解gsoap的原理与思想。只是此项工程工作浩繁,须怀有革命的大无畏精神尚可完成也。

如今唯先写一序,实则有督促之意耳。

2.2 gsoap文档翻译计划(1、介绍)
 
1.介绍
 gSOAP编译工具提供了一个SOAP/XML 关于C/C++ 语言的实现,从而让C/C++语言开发web服务或客户端程序的工作变得轻松了很多。绝大多数的C++web服务工具包提供一组API函数类库来处理特定的SOAP数据结构,这样就使得用户必须改变程序结构来适应相关的类库。与之相反,gSOAP利用编译器技术提供了一组透明化的SOAP API,并将与开发无关的SOAP实现细节相关的内容对用户隐藏起来。gSOAP的编译器能够自动的将用户定义的本地化的C或C++数据类型转变为符合XML语法的数据结构,反之亦然。这样,只用一组简单的API就将用户从SOAP细节实现工作中解脱了出来,可以专注与应用程序逻辑的实现工作了。gSOAP编译器可以集成C/C++和Fortran代码(通过一个Fortran到C的接口),嵌入式系统,其他SOAP程序提供的实时软件的资源和信息;可以跨越多个操作系统,语言环境以及在防火墙后的不同组织。
 gSOAP使编写web服务的工作最小化了。gSOAP编译器生成SOAP的代码来序列化或反序列化C/C++的数据结构。gSOAP包含一个WSDL生成器,用它来为你的web服务生成web服务的解释。gSOAP的解释器及导入器可以使用户不需要分析web服务的细节就可以实现一个客户端或服务端程序。
 下面是gSOAP的一些特点:
 ×gSOAP编译器可以根据用户定义的C和C++数据结构自动生成符合SOAP的实例化代码。
 ×gSOAP支持WSDL 1.1, SOAP 1.1, SOAP 1.2, SOAP RPC 编码方式以及 literal/document 方式.
 ×gSOAP是少数完全支持SOAP1.1 RPC编码功能的工具包,包括多维数组及动态类型。比如,一个包含一个基类参数的远程方法可以接收客户端传来的子类实例。子类实例通过动态绑定技术来保持一致性。
 ×gSOAP 支持 MIME (SwA) 和 DIME 附件包。
 ×gSOAP是唯一支持DIME附件传输的工具包。它允许你在保证XML可用性的同时能够以最快的方式(流方式)传递近乎无大小限制的二进制数据。
 ×gSOAP 支持 SOAP-over-UDP。
 ×gSOAP 支持 IPv4 and IPv6.
 ×gSOAP 支持 Zlib deflate and gzip compression (for HTTP, TCP/IP, and XML file storage)。
 ×gSOAP 支持 SSL (HTTPS)。
 ×gSOAP 支持 HTTP/1.0, HTTP/1.1 保持连接, 分块传输及基本验证。
 ×gSOAP 支持 SOAP 单向消息。
 ×gSOAP 包含一个 WSDL 生成器,便于web服务的发布。
 ×gSOAP 包含一个WSDL解析器 (将WSDL转换为gSOAP头文件),可以自动化用户客户端及服务端的开发。
 ×生成可以单独运行的web服务及客户端程序。
 ×因为只需要很少内存空间,所以可以运行在类似Palm OS, Symbian, Pocket PC的小型设备中。
 ×适用于以C或C++开发的web服务中。
 ×跨平台:Windows, Unix, Linux, Mac OS X, Pocket PC, Palm OS, Symbian等。
 ×支持序列化程序中的本地化C/C++数据结构。
 ×可以使用输入和输出缓冲区来提高效率,但是不用完全消息缓冲来确定HTTP消息的长度。取而代之的是一个三相序列化方法。这样,像64位编码的图像就可以在小内存设备(如PDA)中以DIME附件或其他方式传输。
 ×支持C++单继承,动态绑定,重载,指针结构(列表、树、图、循环图,定长数组,动态数组,枚举,64位2进制编码及16进制编码)。
 ×不需要重写现有的C/C++应用。但是,不能用unions,指针和空指针来作为远程方法调用参数的数据结构中元素。
 ×三相编组:1)分析指针,引用,循环数据结构;2)确定HTTP消息长度;3)将数据序列化位SOAP1.1编码方式或用户定义的数据编码方式。
 ×双相编组:1)SOAP解释及编码;2)分解“forward”指针(例如:分解SOAP中的href属性)。
 ×完整可定制的SOAP错误处理机制。
 ×可定制的SOAP消息头处理机制,可以用来保持状态信息

2.3 gsoap文档翻译计划(2、符号规定)
 
懒得翻译了,因为也不能再现出原来的版式。后面章节中分别注明吧,

2.4 gsoap文档翻译计划(3)
 
3. gSoap2.5版与gSOAP 2.4版(或以前版本)的不同
 按照WS-I Basic Profile 1.0a的要求,gSOAP2.5及以上版本默认使用SOAP RPC文字。这不需要去关心,因为WSDL解析器wsdl2h在你提供一个WSDL文档时,会自动注意这些不同点。增加了一个soapcpp2编译器的编译选项 -e ,用来保持与gSOAP2.4及以前版本的兼容性。

2.5 gsoap文档翻译计划(4)
 
4  gSoap2.2版与gSOAP 2.1版(或以前版本)的不同
 如果你是从2.1版升级到2.2或以后版本,请注意这些变化。
 为了能够分离传输、内容编码、映射中的接收/发送设置,改变了运行时选项及标志。这些标志分布再四个类中:传输(IO),内容编码(ENC),XML编组(XML)及C/C++数据映射。不再提倡使用旧标志soap_disable_X及soap_enable_X(其中,X表示选项名)。具体内容请参见9.12节。

2.6 gsoap文档翻译计划(5)
 
5. gSoap2.x版与gSOAP 1.x版的不同
 如果你是从1.x版升级到2.x版,请注意下面的内容。
 gSOAP2.0及之后的版本是在1.x版基础上重写的。gSOAP2.0之后的版本是线程安全的,但之前版本不是。gSOAP2.x版本中的主要文件已经重新命名,以便与1.x版区分。

gSOAP 1.X  gSOAP 2.X 
soapcpp  soapcpp2 
soapcpp.exe  soapcpp2.exe 
stdsoap.h  stdsoap2.h 
stdsoap.c  stdsoap2.c 
stdsoap.cpp  stdsoap2.cpp 
 
 从1.x版升级到2.x版并不需要进行大量的代码重写工作。所有2.x版相关的函数都定义在stdsoap2.c[pp]文件中,这个文件是由gSOAP编译器自动生成的。所以,用1.x版开发的服务端或客户端代码需要进行修改以适应2.x版中函数的变化:在2.x版中,所有的gSOAP函数都增加了一个参数用来保存一个gSOAP运行环境实例。这个参数包括了文件描述,表,缓冲,标志位等,它在所有gSOAP函数中都是第一个参数。
 gSOAP运行环境实例是一个struct soap类型的变量。当客户端程序访问远程方法前或当服务端程序能够接收一个请求前,必须先将这个运行环境变量初始化。在2.x版中新增了3个函数来负责这些事情:
 函数                        解释
 soap_init(struct soap *soap)         初始化环境变量(只需执行一次)
 struct soap *soap_new()            定义并初始化环境变量并返回一个该变量的指针
 struct soap *soap_copy(struct soap *soap)   定义一个环境变量并从已有的环境变量中拷贝环境信息
 
  环境变量定义好后就可以重复使用而不必再次初始化了。只有当线程独占访问时,我们才需要一个新的环境变量。例如,下面的代码分配了一个用于多个远程方法的环境变量:
 int main()
{
   struct soap soap;
   …
   soap_init(&soap); // 初始化环境变量
   …
   soap_call_ns__method1(&soap, …); // 调用一个远程方法
   …
   soap_call_ns__method2(&soap, …); // 调用另一个远程方法
   …
   soap_end(&soap); // 清除环境变量
   …
}
 我们也可以像下面这样定义环境变量:
 int main()
{
   struct soap *soap;
   …
   soap = soap_new(); // 定义并初始化环境变量
   if (!soap) // 如果不能定义,退出
   …
   soap_call_ns__method1(soap, …); // 调用远程函数
   …
   soap_call_ns__method2(soap, …); // 调用另一个远程函数
   …
   soap_end(soap); // 清除环境变量
   …
   free(soap); // 释放环境变量空间
}
 
 服务端代码在调用soap_serve函数前,需要定义相关环境变量:

int main()
{
   struct soap soap;
   soap_init(&soap);
   soap_serve(&soap);

 或者像下面这样:

int main()
{
   soap_serve(soap_new());

 soap_serve函数用来处理一个或多个(当允许HTTP keep-alive时,参见18.11节中的SOAP_IO_KEEPALIVE标志)请求。
 一个web服务可以用多线程技术来处理请求:
int main()
{
   struct soap soap1, soap2;
   pthread_t tid;
   …
   soap_init(&soap1);
   if (soap_bind(&soap1, host, port, backlog) < 0) exit(1);
   if (soap_accept(&soap1) < 0) exit(1);
   pthread_create(&tid, NULL, (void*(*)(void*))soap_serve, (void*)&soap1);
   …
   soap_init(&soap2);
   soap_call_ns__method(&soap2, …); // 调用远程方法
   …
   soap_end(&soap2);
   …
   pthread_join(tid, NULL); // 等待线程结束
   soap_end(&soap1); // 释放环境变量

 在上面的例子中,需要两个环境变量信息。而在1.x版本中,由于静态分配环境变量,多线程技术是不被允许的(只有一个线程可以用这个环境变量调用远程方法或处理请求信息)。
 8.2.4节将给出一个具体的多线程服务实例,它为每个SOAP请求分配一个独立线程进行处理。
2.7 gsoap文档翻译计划(6)
 
gSOAP 使用下面的软件包验证了其可用性: Apache 2.2 Apache Axis ASP.NET Cape Connect Delphi easySOAP++ eSOAP Frontier GLUE Iona XMLBus kSOAP MS SOAP Phalanx SIM SOAP::Lite SOAP4R Spray SQLData Wasp Adv. Wasp C++ White Mesa xSOAP ZSI 4S4C

2.8 gsoap文档翻译计划(7)
 
7  准备工作

 要开始用gSOAP创建一个web服务应用, 你需要:

 一个C/C++编译器.
 拥有根据操作系统平台创建的可执行的gSOAP的stdsoap2(windows下为stdsoap2.exe)编译器。
 拥有根据操作系统平台创建的可执行的gSOAP的wsdl2h(windows下为wsdl2h.exe)WSDL解析器。
 需要’stdsoap2.c’或’stdsoap2.cpp’及’stdsoap2.h’文件来实现你的SOAP功能。你可以创建一个dll或动态库以便简化连接。
 如果你要支持SSL(HTTPS)及压缩的话,可以安装OpenSSL及Zlib库。
 gSOAP是独立开发包,不需要任何第三方的软件支持(除非你要用到OpenSSL及Zlib)。
 与平台无关的gSOAP版本需要你下面的工具编译’soapcpp2′及’wsdl2h’文件:
 一个C++编译器(用来编译’wsdl2h’WSDL解析器)。
 Bison 或 Yacc
 Flex 或 Lex
 推荐使用Bison及Flex。
 在软件包samples目录下有大量的开发实例。可以用’make’来编译这些例子。这些例子包含了gSOAP中的各个方面。其中,最简单的例子是 one-liners(samples/oneliners)。

2.9 gsoap文档翻译计划(8.1.1)
 
8  快速指南
 本指南旨在让你快速开始你的gSOAP开发之旅。阅读本节的内容,需要你对SOAP 1.1协议及C/C++语法有大体的了解。虽然使用gSOAP编译器可以直接用C/C++开始编写web服务及客户端程序而不需要了解SOAP协议的细节,但是由于我们在本节中使用了大量的实例来说明gSOAP与其他SOAP实现的连接及通讯,所以了解一些SOAP及WSDL协议也是必需的。
 
8.1 如何使用gSOAP编译环境来编译SOAP客户端程序
 通常,一个SOAP客户端应用的实现需要为每个客户端需要调用的远程方法提供一个存根例程(stub routine)。存根例程主要负责编码参数信息;将包含参数信息的调用请求发送给制定的SOAP服务;等待返回结果;将结果中的参数信息编码。客户端程序调用访问远程方法的存根例程就像调用本地方法一样。用C/C++手工别写一个存根例程是个十分痛苦的差使,尤其当远程方法的参数中包含特定的数据结构(如:记录、数组、图等)时。幸运的是,gSOAP包中的’wsdl2h’WSDL解析器和’soapcpp2’存根及架构编译器能够将web服务客户端及服务端的开发工作自动化。
 ’soapcpp2’存根及架构编译器是可以生成构建C++ SOAP客户端所需的C++源码的预编译器。该预编译器的输入参数是一个标准的C/C++头文件。这个头文件可以由WSDL解析器根据相关的WSDL文档自动生成。
 参见下面的命令:
 $ wsdl2h -o quote.h http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl
 上面的命令根据制定URL提供的WSDL文档生成一个C++语法结构的头文件。
 如果需要生成一个纯C的头文件,需要一下命令:
 $ wsdl2h -c -o quote.h http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl
 更多关于WSDL解析器及其选项的细节信息,请参见8.2.10节。
 执行上述命令后,quote.h文件就生成了。其中包含开发客户端或服务端程序的存根例程定义。SOAP服务远程方法以函数声明的方式在这个头文件中被定义。C/C++源代码的存根例程将通过预编译器自动实现。同时,每个远程方法的程序框架也被自动生成了,它可以用来建立SOAP服务端程序应用。
 SOAP服务的输入输出参数可以是简单的数据类型或复杂的数据结构,可以由WSDL解析器自动生成或手工定义。预编译器将自动生成序列化/反序列化这些数据的代码,以便存根例程可以将这些数据以XML的方式编码或解码。

8.1.1  例子
 XMethods Delayed Stock Quote 服务提供一个getQuote方法(由’wsdl2h’WSDL解析器生成的quote.h定义)。这个方法根据提供的股票名称返回相应的股票价格。下面是这个方法的WSDL文档信息:

Endpoint URL:  http://services.xmethods.net:80/soap 
SOAP action:  "" (2 quotes) 
Remote method namespace:  urn:xmethods-delayed-quotes 
Remote method name:  getQuote 
   Input parameter:  symbol of type xsd:string 
   Output parameter:  Result of type xsd:float 
 
 下面是由WSDL解析器生成的getQuote.h头文件(实际的文件内容与’wsdl2h’版本及生成选项有关):
//gsoap ns1 service name: net_DOTxmethods_DOTservices_DOTstockquote_DOTStockQuoteBinding
//gsoap ns1 service type: net_DOTxmethods_DOTservices_DOTstockquote_DOTStockQuotePortType
//gsoap ns1 service port: http://66.28.98.121:9090/soap
//gsoap ns1 service namespace: urn:xmethods-delayed-quotes
//gsoap ns1 service documentation: Definitions generated by the gSOAP WSDL parser 1.0
// Service net.xmethods.services.stockquote.StockQuoteService : net.xmethods.services.stockquote.StockQuote web service

//gsoap ns1 service method-style: getQuote rpc
//gsoap ns1 service method-encoding: getQuote http://schemas.xmlsoap.org/soap/encoding/
//gsoap ns1 service method-action: getQuote urn:xmethods-delayed-quotes#getQuote
int ns1__getQuote(char *symbol, float &Result);

 这个头文件用C/C++代码为gSOAP预编译器指定了web服务的细节。远程方法被定义为函数ns1__getQuote,同时指定了客户端调用web服务所需的所有细节信息。
 getQuote远程方法需要一个名为symbol的字符串作为输入参数,同时需要一个名为Result的浮点数作为输出参数。预编译器生成的远程方法调用函数中,最后一个参数必须是输出参数,该参数以引用方式传递或定义为指针类型。除此之外的所有参数都是输入参数,这些参数必须以传值方式传递。函数返回一个整型值,其值说明web服务调用成功或出现的错误。具体的错误代码信息参见10.2节。
 函数名中命名空间前缀ns1__的细节信息将在8.1.2节中讨论。简单的说,命名空间前缀与函数名之间用两个下划线分割。比如,ns1__getQuote中,ns1为命名空间前缀,getQuote是函数名称。(函数名中单个下划线将在XML中解释为破折号-,因为在XML中破折号比下划线更常用,细节请参见10.3节)
 用下面命令执行预编译器:
 
soapcpp2 getQuote.h 

 预编译器根据getQuote.h中定义的信息来生成存根例程的代码框架。这个存根例程可以在客户端程序中随处调用。存根例程被声明为下面的样子:

int soap_call_ns1__getQuote(struct soap *soap, char *URL, char *action, char *symbol, float &Result); 

 存根例程保存在soapClient.cpp文件中;soapC.cpp文件包含了序列化、反序列化数据的函数。你可以用 -c编译选项来生成纯C的代码。
 注意,soap_call_ns1__getQuote在ns1__getQuote的参数基础上又增加了三个参数:soap必须是指向一个gSOAP运行环境的合法指针;URL是web服务的URL;action指明了web服务需要的SOAP action。XMethods Delayed Stock Quote服务的URL是http://66.28.98.121:9090/soap,action是"" (2 quotes)。你可以动态的改变URL及action。如果上述两个变量定义为NULL,则会使用头文件中定义的信息。
 下面的例子调用远程方法获取IBM的股票信息:

#include "soapH.h" // 包含生成的存根例程定义
#include "net_DOT_xmethods_DOT_services_DOT_stockquote_DOT_StockQuoteBinding.nsmap" // 包含命名空间表
int main()
{
   struct soap soap; // gSOAP运行环境
   float quote;
   soap_init(&soap); // 初始化运行环境(只执行一次)
   if (soap_call_ns1__getQuote(&soap, NULL, NULL, "IBM", &quote) == SOAP_OK)
      std::cout << "Current IBM Stock Quote = " << quote << std::endl;
   else // an error occurred
      soap_print_fault(&soap, stderr); // 在stderr中显示错误信息
   soap_destroy(&soap); // 删除类实例(仅用于C++中)
   soap_end(&soap); // 清除运行环境变量
   soap_done(&soap); // 卸载运行环境变量
   return 0;

 调用成功后,存根例程返回SOAP_OK同时quote变量保存着股票信息;如果调用失败则产生一个错误,同时通过soap_print_fault函数显示错误信息。
 gSOAP编译器同时为C++客户端程序生成了一个代理类。

#include "soapnet_DOT_xmethods_DOT_services_DOT_stockquote_DOT_StockQuoteBindingProxy.h" // 获得代理
#include "net_DOT_xmethods_DOT_services_DOT_stockquote_DOT_StockQuoteBinding.nsmap" // 包含命名空间表
int main()
{
   net q; // "net" 是这个服务代理类的短名称
   float r;
   if (q.ns1__getQuote("IBM", r) == SOAP_OK)
      std::cout << r << std::endl;
   else
      soap_print_fault(q.soap, stderr);
   return 0;

 代理类的构造函数定义并初始化了一个gSOAP环境变量实例。所有的HTTP及SOAP/XML处理被隐藏在后台处理。你可以改变WSDL解析器生成的头文件中web服务的名称。web服务的名字是由WSDL内容中萃取的,并不总是短名称格式的。你可以随意更改这个条目

//gsoap ns1 service name: net_DOT_xmethods_DOT_services_DOT_stockquote_DOT_StockQuoteBinding 

来使用一个更合适的名字。这个名字将决定代理类文件及XML命名空间表文件的名字。
 下面的函数可以用来建立一个gSOAP运行环境(struct soap):
soap_init(struct soap *soap)  初始化运行环境变量(只需要执行一次)
soap_init1(struct soap *soap, soap_mode iomode)  初始化运行环境变量同时设置in/out模式
soap_init2(struct soap *soap, soap_mode imode, soap_mode omode)  初始化运行环境变量同时分别设置in/out模式
struct soap *soap_new()  定义、初始化运行环境并返回一个执行运行环境的指针
struct soap *soap_new1(soap_mode iomode)  定义、初始化运行环境并返回一个执行运行环境的指针并设置in/out模式
struct soap *soap_new2(soap_mode imode, soap_mode omode)  定义、初始化运行环境并返回一个执行运行环境的指针并分别设置in/out模式
struct soap *soap_copy(struct soap *soap)  定义一个新的环境变量并将现有环境信息赋值给新的变量
soap_done(struct soap *soap)  重置、关闭连接,清除环境变量
 
 环境变量可以在程序中任意次数的使用。每个新的线程就需要一个新的环境变量实例。
 当例子中的客户端程序执行时,SOAP请求通过soap_call_ns1__getQuote函数来调用,它生成下面的SOAP RPC请求信息:
POST /soap HTTP/1.1
Host: services.xmethods.net
Content-Type: text/xml
Content-Length: 529
SOAPAction: ""

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
   xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   xmlns:ns1="urn:xmethods-delayed-quotes"
   SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:getQuote>
<symbol>IBM</symbol>
</ns1:getQuote>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope> 

 XMethods Delayed Stock Quote 服务返回如下的信息:
HTTP/1.1 200 OK
Date: Sat, 25 Aug 2001 19:28:59 GMT
Content-Type: text/xml
Server: Electric/1.0
Connection: Keep-Alive
Content-Length: 491

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
   soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/?
<soap:Body>
<n:getQuoteResponse xmlns:n="urn:xmethods-delayed-quotes?
<Result xsi:type="xsd:float?41.81</Result>
</n:getQuoteResponse>
</soap:Body>
</soap:Envelope>

 服务返回的信息通过存根例程来解析,并保存在soap_call_ns1__getQuote函数的quote参数中。
 客户端程序可以在任意时间多次调用远程方法。请看下面的例子:


struct soap soap;
float quotes[3]; char *myportfolio[] = {"IBM", "MSDN"};
soap_init(&soap); // need to initialize only once
for (int i = 0; i < 3; i++)
   if (soap_call_ns1__getQuote(&soap, "http://services.xmethods.net:80/soap", "", myportfolio[i], &quotes[i]) != SOAP_OK)
      break;
if (soap.error) // an error occurred
   soap_print_fault(&soap, stderr);
soap_end(&soap); // clean up all deserialized data
… 

 这个客户端程序通过调用ns1__getQuote存根例程来为数组中的每个股票代码获得信息。
 上面的例子给我们示范了使用gSOAP创建一个SOAP客户端时多么容易的事情啊。
2.10 gSoap文档翻译计划(8.1.2~8.1.3)
 
8.1.2 关于命名空间
 函数ns1__getQuote(上节提到的)中,使用了ns1__作为远程方法的命名空间。使用命名空间是为了防止远程方法名冲突,比方多个服务中使用同一个远程方法名的情况。
 命名空间前缀及命名空间名称同时也被用来验证SOAP信息的内容有效性。存根例程通过命名空间表中的信息来验证服务返回信息。命名空间表在运行时被取出用于解析命名空间绑定,反序列化数据结构,解码并验证服务返回信息。命名空间表不应该包含在gSOAP预编译器所需输入的头文件中。在18.2节中将会对命名空间表做详细介绍。
 Delayed Stock Quote服务客户端的命名空间表如下:

struct Namespace namespaces[] =
{   // {"命名前缀", "空间名称"}
   {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, // 必须是第一行
   {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"}, // 必须是第二行
   {"xsi", "http://www.w3.org/2001/XMLSchema-instance"}, // 必须是第三行
   {"xsd", "http://www.w3.org/2001/XMLSchema"}, // 2001 XML 大纲
   {"ns1", "urn:xmethods-delayed-quotes"}, // 通过服务描述获取
   {NULL, NULL} // 结束
};

 第一行命名空间是SOAP1.1协议默认命名空间。事实上,命名空间表就是用来让程序员可以规定SOAP编码方式,能够用包含命名空间的命名空间前缀来满足指定SOAP服务的命名空间需求的。举例来说,使用前面命名空间表中定义的命名空间前缀ns1,存根例程就可以对getQuote方法的请求进行编码。这个过程由gSOAP预编译器通过在getQuote.h文件中定义的包含前缀ns1的ns1__getQuote函数自动完成。通常,如果要在远程方法名,结构名,类名,字段名等结构或类中使用命名空间前缀,就必须在命名空间表中进行定义。
 命名空间表将会被存根例程封装,并按下面的形式输出:


<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
   xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   xmlns:ns1="urn:xmethods-delayed-quotes"
   SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
… 

 这个命名空间绑定将被SOAP服务用来验证SOAP请求。

8.1.3 例子
 使用命名空间前缀可以解决在不同的服务中使用相同名称的远程方法的问题,看下面的例子:

// Contents of file "getQuote.h":
int ns1__getQuote(char *symbol, float &Result);
int ns2__getQuote(char *ticker, char *&quote); 

 这个例子允许客户端程序使用不同的命名空间以便连接到不同的服务程序执行其中的远程方法。
 命名空间前缀也可以用在类声明中使用,在XML大纲中区分同名但不同命名空间的SOAP值。例如:

class e__Address // an electronic address
{
   char *email;
   char *url;
};
class s__Address // a street address
{
   char *street;
   int number;
   char *city;
}; 

 在生成的序列化函数中,使用e__Address的一个实例来表示e命名空间前缀的一个地址元素类型。

<e:Address xsi:type="e:Address">
<email xsi:type="string">me@home</email>
<url xsi:type="string">www.me.com</url>
</e:Address> 

 用s__Address的一个实例来表示s命名空间前缀的一个地址元素类型。

<s:Address xsi:type="s:Address">
<street xsi:type="string">Technology Drive</street>
<number xsi:type="int">5</number>
<city xsi:type="string">Softcity</city>
</s:Address> 

 客户端程序的命名空间表必须有e和s的数据类型定义:

struct Namespace namespaces[] =
{ …
   {"e", "http://www.me.com/schemas/electronic-address"},
   {"s", "http://www.me.com/schemas/street-address"},
… 

 命名空间表必须作为客户端程序的一部分,使客户端程序在运行时可以对数据进行序列化及反序列化。
2.11 gsoap翻译计划(8.1.4~8.1.6)
 
8.1.4 如何建立客户端程序代理类
 用于C++客户端程序的代理类信息是由gSOAP预编译器自动创建的。为了说明代理类的生成过程,我们在getQuote.h头文件中加入一些信息,以便gSOAP预编译器可以生成代理类。这些信息就类似于WSDL解析器自动生成的头文件中就已经包含的信息。

//"getQuote.h"的内容:
//gsoap ns1 service name: Quote
//gsoap ns1 service location: http://services.xmethods.net/soap
//gsoap ns1 service namespace: urn:xmethods-delayed-quotes
//gsoap ns1 service style: rpc
//gsoap ns1 service encoding: encoded
//gsoap ns1 service method-action: getQuote ""
int ns1__getQuote(char *symbol, float &Result); 

 前三行指令用于定义代理类的名称,服务地址,命名空间。第四行、第五行指令定义了使用SOAP RPC编码方式。最后一行定义了可选的SOAPAction。当需要SOAPAction时,这行信息将提供给每个远程方法。使用soapcpp2对该头文件进行编译后,将会产生soapQuoteProxy.h文件。它包含下面的内容:

#include "soapH.h"
class Quote
{ public:
   struct soap *soap;
   const char *endpoint;
   Quote() { soap = soap_new(); endpoint = "http://services.xmethods.net/soap"; };
   ~Quote() { if (soap) { soap_destroy(soap); soap_end(soap); soap_done(soap); free((void*)soap); }};
   int getQuote(char *symbol, float &Result) { return soap ? soap_call_ns1__getQuote(soap, endpoint, "", symbol, Result) : SOAP_EOM; };
}; 

 为了能够在运行时刻对gSOAP环境变量及命名空间进行定制,上述两个变量被定义程全局变量。
 生成的代理类可以同命名空间表一起包含在客户端程序中,请看下面的例子:

#include "soapQuoteProxy.h" // 获得代理类
#include "Quote.nsmap" // 获得命名空间绑定
int main()
{
   Quote q;
   float r;
   if (q.ns1__getQuote("IBM", r) == SOAP_OK)
      std::cout << r << std::endl;
   else
      soap_print_fault(q.soap, stderr);
   return 0;

 Quote构造函数定义并初始化了一个gSOAP运行环境实例。所有的HTTP及SOAP/XML进程都被隐藏在后台自动执行。
 如果你需要多个命名空间表或要联合多个客户端,你可以在执行soapcpp2时添加参数-n及-p来生成命名空间表以防止冲突。详细信息请看9.1及18.33节。同时,你可以使用C++代码命名空间来创建一个命名空间限制的代理类,详细信息请看18.32节。

8.1.5  XSD 类型编码
 许多SOAP服务需要在SOAP负载中使用XML编码。在gSOAP预编译器中使用的默认编码为SOAP RPC编码。然而,使用XSD类型编码可以改善互操作性。XSD类型在头文件中用typedef定义。举个例子,下面的定义将C/C++类型转换为XSD类型:

// Contents of header file:

typedef char *xsd__string; // encode xsd__string value as the xsd:string schema type
typedef char *xsd__anyURI; // encode xsd__anyURI value as the xsd:anyURI schema type
typedef float xsd__float; // encode xsd__float value as the xsd:float schema type
typedef long xsd__int; // encode xsd__int value as the xsd:int schema type
typedef bool xsd__boolean; // encode xsd__boolean value as the xsd:boolean schema type
typedef unsigned long long xsd__positiveInteger; // encode xsd__positiveInteger value as the xsd:positiveInteger schema type
… 

 这些简单的声明告诉gSOAP预编译器当远程方法参数中使用上述定义的类型时,就把相关的C++类型转当作内建的XSD类型进行编码、解码。同时,使用typedef不需要使用内建C++类型的客户端或服务端程序更改现有代码(但只是当参数为简单的C++类型时,请参看11.2.2节来使用XSD类型表示组合的数据类型)。

8.1.6 例子
 
 重新考虑一席getQuote的例子。现在用XSD类型来重写代码:

// Contents of file "getQuote.h":
typedef char *xsd__string;
typedef float xsd__float;
int ns1__getQuote(xsd__string symbol, xsd__float &Result); 

 使用预编译器编译这个头文件,将会生成与原来相同的存根例程代码:

int soap_call_ns1__getQuote(struct soap *soap, char *URL, char *action, char *symbol, float &Result); 

 客户端程序不需要进行任何改动,即可使用XSD类型类编码、解码数据类型信息了。
 举个例子,当客户端程序调用代理时,代理方法用xsd:string类型产生一个SOAP请求:


<SOAP-ENV:Body>
<ns1:getQuote><symbol xsi:type="xsd:string">IBM</symbol>
</ns1:getQuote>
</SOAP-ENV:Body>
… 

服务端的返回码为:


<soap:Body>
<n:getQuoteResponse xmlns:n="urn:xmethods-delayed-quotes">
<Result xsi:type="xsd:float">41.81</Result>
</n:getQuoteResponse>
</soap:Body>
… 

 

2006年11月11日

–据悉,国内金融界从中国国情出发,已经开始共建金融权威认证中心(CA)。这项计划由银行卡信息交换总中心负责筹建,经金融信息化领导小组正式批准,今年3月已开始向国内外厂商发出招标通告。CA只是电子商务信息安全大系统中的一个环节,这里我们将向读者全面系统介绍网络安全中的一个重要概念及技术。

–随着Internet技术的发展,Intranet服务、电子商务、网络银行成为近年来的几大热点,国内也有越来越多的机构、企业涉足这些领域。这样,信息安全问题就提到日程上来。几年来,在Internet上涌现出了许多新的安全技术和安全规范,公开密钥基础设施(PKI:PublicKeyInfrastructure)即是其中一员。  

 PKI的基本组成

–PKI是一种遵循标准的密钥管理平台,它能够为所有网络应用透明地提供采用加密和数字签名等密码服务所必需的密钥和证书管理。PKI必须具有认证机关(CA)、证书库、密钥备份及恢复系统、证书作废处理系统、客户端证书处理系统等基本成分,构建PKI也将围绕着这五大系统来构建。

–1.认证机关

–CA是证书的签发机构,它是PKI的核心。众所周知,构建密码服务系统的核心内容是如何实现密钥管理,公钥体制涉及到一对密钥,即私钥和公钥,私钥只由持有者秘密掌握,无须在网上传送,而公钥是公开的,需要在网上传送,故公钥体制的密钥管理主要是公钥的管理问题,目前较好的解决方案是引进证书(certificate)机制。

–证书是公开密钥体制的一种密钥管理媒介。它是一种权威性的电子文档,形同网络计算环境中的一种身份证,用于证明某一主体(如人、服务器等)的身份以及其公开密钥的合法性。在使用公钥体制的网络环境中,必须向公钥的使用者证明公钥的真实合法性。因此,在公钥体制环境中,必须有一个可信的机构来对任何一个主体的公钥进行公证,证明主体的身份以及他与公钥的匹配关系。

CA正是这样的机构,它的职责归纳起来有:  

验证并标识证书申请者的身份;   

确保CA用于签名证书的非对称密钥的质量;  

确保整个签证过程的安全性,确保签名私钥的安全性;   

证书材料信息(包括公钥证书序列号、CA标识等)的管理;   

确定并检查证书的有效期限;  

确保证书主体标识的唯一性,防止重名;发布并维护作废证书表;  

对整个证书签发过程做日志记录;   

向申请人发通知。

–其中最为重要的是CA自己的一对密钥的管理,它必须确保其高度的机密性,防止他方伪造证书。CA的公钥在网上公开,整个网络系统必须保证完整性。

–证书的主要内容如右表所示。

–其中,CA的数字签名保证了证书(实质是持有者的公钥)的合法性和权威性。   

主体(用户)的公钥可有两种产生方式:用户自己生成密钥对,然后将公钥以安全的方式传送给CA,该过程必须保证用户公钥的可验证性和完整性;   

CA替用户生成密钥对,然后将其以安全的方式传送给用户,该过程必须确保密钥对的机密性、完整性和可验证性。该方式下由于用户的私钥为CA所知,故对CA的可信性有更高的要求。

–用户A可通过两种方式获取用户B的证书和公钥,一种是由B将证书随同发送的正文信息一起传送给A,另一种是所有的证书集中存放于一个证书库中,用户A可从该地点取得B的证书。

–CA的公钥可以存放在所有节点处,方便用户使用。  

 表证书的主要内容

–上表中的"用途"是一项重要的内容,它规定了该证书所公证的公钥的用途。公钥必须按规定的用途来使用。一般地,公钥有两大类用途:用于验证数字签名。消息接收者使用发送者的公钥对消息的数字签名进行验证。用于加密信息。消息发送者使用接收者的公钥加密用于加密消息的密钥,进行数据加密密钥的传递。

–相应地,系统中需要配置用于数字签名/验证的密钥对和用于数据加密/脱密的密钥对,这里分别称为签名密钥对和加密密钥对。这两对密钥对于密钥管理有不同的要求:   

签名密钥对

–签名密钥对由签名私钥和验证公钥组成。签名私钥具有日常生活中公章、私章的效力,为保证其唯一性,签名私钥绝对不能够作备份和存档,丢失后只需重新生成新的密钥对,原来的签名可以使用旧公钥的备份来验证。验证公钥需要存档,用于验证旧的数字签名。

–用作数字签名的这一对密钥一般可以有较长的生命期。  

加密密钥对

–加密密钥对由加密公钥和脱密私钥组成。

–为防止密钥丢失时丢失数据,脱密私钥应该进行备份,同时还可能需要进行存档,以便能在任何时候脱密历史密文数据。加密公钥无须备份和存档,加密公钥丢失时,只须重新产生密钥对。

–加密密钥对通常用于分发会话密钥,这种密钥应该频繁更换,故加密密钥对的生命周期较短。

–不难看出,这两对密钥的密钥管理要求存在互相冲突的地方,因此,系统必须针对不同的用途使用不同的密钥对,尽管有的公钥体制算法,如目前使用广泛的RSA,既可以用于加密、又可以用于签名,在使用中仍然必须为用户配置两对密钥、两张证书,其一用于数字签名,另一用于加密。

–2.证书库

–证书库是证书的集中存放地,它与网上"白页"类似,是网上的一种公共信息库,用户可以从此处获得其他用户的证书和公钥。

–构造证书库的最佳方法是采用支持LDAP协议的目录系统,用户或相关的应用通过LDAP来访问证书库。系统必须确保证书库的完整性,防止伪造、篡改证书。

–3.密钥备份及恢复系统—-如果用户丢失了用于脱密数据的密钥,则密文数据将无法被脱密,造成数据丢失。为避免这种情况的出现,PKI应该提供备份与恢复脱密密钥的机制。

–密钥的备份与恢复应该由可信的机构来完成,例如CA可以充当这一角色。值得强调的是,密钥备份与恢复只能针对脱密密钥,签名私钥不能够作备份。

–4.证书作废处理系统

–证书作废处理系统是PKI的一个重要组件。同日常生活中的各种证件一样,证书在CA为其签署的有效期以内也可能需要作废,例如,A公司的职员a辞职离开公司,这就需要终止a证书的生命期。为实现这一点,PKI必须提供作废证书的一系列机制。作废证书有如下三种策略:   

作废一个或多个主体的证书;   

作废由某一对密钥签发的所有证书;  

作废由某CA签发的所有证书。

–作废证书一般通过将证书列入作废证书表(CRL)来完成。通常,系统中由CA负责创建并维护一张及时更新的CRL,而由用户在验证证书时负责检查该证书是否在CRL之列。CRL一般存放在目录系统中。

–证书的作废处理必须在安全及可验证的情况下进行,系统还必须保证CRL的完整性。

–5.PKI应用接口系统

–PKI的价值在于使用户能够方便地使用加密、数字签名等安全服务,因此一个完整的PKI必须提供良好的应用接口系统,使得各种各样的应用能够以安全、一致、可信的方式与PKI交互,确保所建立起来的网络环境的可信性,同时降低管理维护成本。

–为了向应用系统屏蔽密钥管理的细节,PKI应用接口系统需要实现如下的功能:   

完成证书的验证工作,为所有应用以一致、可信的方式使用公钥证书提供支持;   

以安全、一致的方式与PKI的密钥备份与恢复系统交互,为应用提供统一的密钥备份与恢复支持;   

在所有应用系统中,确保用户的签名私钥始终只在用户本人的控制之下,阻止备份签名私钥的行为;  

根据安全策略自动为用户更换密钥,实现密钥更换的自动、透明与一致;   

为方便用户访问加密的历史数据,向应用提供历史密钥的安全管理服务;  

为所有应用访问统一的公用证书库提供支持;  

以可信、一致的方式与证书作废系统交互,向所有应用提供统一的证书作废处理服务;  

完成交叉证书(见后)的验证工作,为所用应用提供统一模式的交叉验证支持;

支持多种密钥存放介质,包括IC卡、PC卡、安全文件等。

–最后,PKI应用接口系统应该是跨平台的。  

PKI的功能

–归纳起来,PKI应该为应用提供如下的安全支持:   

证书与CA

–PKI应实现CA以及证书库、CRL等基本的证书管理功能。  

密钥备份及恢复证书、密钥对的自动更换

–证书、密钥都有一定的生命期限。当用户的私钥泄露时,必须更换密钥对;另外,随着计算机速度日益提高,密钥长度也必须相应地增长。因此,PKI应该提供完全自动(无须用户干预)的密钥更换以及新证书的分发工作。  

交叉签证

–每个CA只可能覆盖一定的作用范围,即CA的域,例如,不同的企业往往有各自的CA,它们颁发的证书都只在企业范围内有效。当隶属于不同CA的用户需要交换信息时,就需要引入交叉证书和交叉验证,这也是PKI必须完成的工作。  

加密密钥和签名密钥的分隔

–如前所述,加密和签名密钥的密钥管理需求是相互抵触的,因此PKI应该支持加密和签名密钥的分隔使用。  

 支持对数字签名的不可抵赖

–任何类型的电子商务都离不开数字签名,因此PKI必须支持数字签名的不可抵赖性,而数字签名的不可抵赖性依赖于签名私钥的唯一性和机密性,为确保这一点,PKI必须保证签名密钥与加密密钥的分隔使用。  

密钥历史的管理

–每次更新加密密钥后,相应的解密密钥都应该存档,以便将来恢复用旧密钥加密的数据。每次更新签名密钥后,旧的签名私钥应该妥善销毁,防止破坏其唯一性;相应的旧验证公钥应该进行存档,以便将来用于验证旧的签名。这些工作都应该是PKI自动完成的。  

PKI的性能要求

–作为网络环境的一种基础设施,PKI必须具有良好的性能,一般地,对PKI的性能有如下要求:

透明性和易用性

–这是对PKI的最基本要求,PKI必须尽可能地向上层应用屏蔽密码服务的实现细节,向用户屏蔽复杂的安全解决方案,使密码服务对用户而言简单易用,同时便于单位、企业完全控制其信息资源。  

可扩展性

–证书库和CRL必须具有良好的可扩展性。

互操作性

–不同企业、单位的PKI实现可能是不同的,这就提出了互操作性要求。要保证PKI的互操作性,必须将PKI建立在标准之上,这些标准包括加密标准、数字签名标准、HASH标准、密钥管理标准、证书格式、目录标准、文件信封格式、安全会话格式、安全应用程序接口规范等。  

 支持多应用 –PKI应该面向广泛的网络应用,提供文件传送安全、文件存储安全、电子邮件安全、电子表单安全、Web应用安全等保护。

支持多平台

–PKI应该支持目前广泛使用的操作系统平台,包括Windows、UNIX、MAC等
 

2006年11月09日

程序文档,曾经是程序员的一个头痛问题。写一个程序文档,比较花时间,但不是很难;麻烦的是当程序修改后,程序文档也要跟着同步更新,否则文档和程序就要脱节,文档也就变成没用的东西了。

好在有许多好用的文档生成器来解决这个问题。目前比较流行的C++文档生成器是doxygen。
本文就简单的介绍一下doxygen的文档注释方法,以供初学者参考:

1. 模块定义(单独显示一页)

/*
 * @defgroup 模块名 模块的说明文字
 * @{
 */
 
 … 定义的内容 …
 
/** @} */ // 模块结尾
 
2. 分组定义(在一页内分组显示)
/*
 * @name 分组说明文字
 * @{
 */
 
 … 定义的内容 …
 
/** @} */
 
3. 变量、宏定义、类型定义简要说明
/** 简要说明文字 */
#define FLOAT float
 
/** @brief 简要说明文字(在前面加 @brief 是标准格式) */
#define MIN_UINT 0
 
/*
 * 分行的简要说明 \n
 *  这是第二行的简要说明
 */
int b;
 
4. 函数说明
/*
 * 简要的函数说明文字 
 *  @param [in] param1 参数1说明
 *  @param [out] param2 参数2说明
 *  @return 返回值说明
 */
int func(int param1, int param2);
 
/*
 * 打开文件 \n
 *  文件打开成功后,必须使用 ::CloseFile 函数关闭。
 *  @param[in] file_name 文件名字符串
 *  @param[in] file_mode 文件打开模式字符串,可以由以下几个模块组合而成:
 *  - r 读取
 *  - w 可写
 *  - a 添加
 *  - t 文本模式(不能与 b 联用)
 *  - b 二进制模式(不能与 t 联用)
 *  @return 返回文件编号
 *  - -1 表示打开文件失败
 
 *  @note 文件打开成功后,必须使用 ::CloseFile 函数关闭
 *  @par 示例:
 *  @code
    // 用文本只读方式打开文件
    int f = OpenFile("d:\\test.txt", "rt");
 *  @endcode
 
 *  @see ::ReadFile ::WriteFile ::CloseFile
 *  @deprecated 由于特殊的原因,这个函数可能会在将来的版本中取消。
 */
int OpenFile(const char* file_name, const char* file_mode);
 
5. 枚举类型定义
/** 枚举常量 */
typedef enum TDayOfWeek
{
SUN = 0, /**<  星期天(注意,要以 “<” 小于号开头) */
MON = 1, /**<  星期一 */
TUE = 2, /**<  星期二 */
WED = 3, /**<  星期三 */
THU = 4, /**<  星期四 */
FRI = 5, /**<  星期五 */
SAT = 6  /**<  星期六 */
}
/** 定义类型 TEnumDayOfWeek */
TEnumDayOfWeek;  
  
6. 项目符号标记
  /* 
   *  A list of events:
   *    – mouse events
   *         -# mouse move event
   *         -# mouse click event\n
   *            More info about the click event.
   *         -# mouse double click event
   *    – keyboard events
   *         -# key down event
   *         -# key up event
   *
   *  More text here.
   */
 

结果为:

A list of events:

  • mouse events
    1. mouse move event
    2. mouse click event
      More info about the click event.
    3. mouse double click event
  • keyboard events
    1. key down event
    2. key up event

More text here.

代码示范:
/**//*
 * @defgroup EXAMPLES 自动注释文档范例
 * @author  沐枫
 * @version 1.0
 * @date    2004-2005
 * @{
 */
/**//*
 * @name 文件名常量
 * @{
 */
/**//** 日志文件名 */
#define LOG_FILENAME "d:\\log\\debug.log"
/**//** 数据文件名 */
#define DATA_FILENAME "d:\\data\\detail.dat"
/**//** 存档文件名 */
#define BAK_FILENAME "d:\\data\\backup.dat"
/**//** @}*/ // 文件名常量
/**//*
 * @name 系统状态常量
 *  @{
 */
 
/**//** 正常状态 */
#define SYS_NORMAL 0
/**//** 故障状态 */
#define SYS_FAULT 1
/**//** 警告状态 */
#define SYS_WARNNING 2
/**//** @}*/ // 系统状态常量
/**//** 枚举常量 */
typedef enum TDayOfWeek
{
        SUN = 0, /**//**< 星期天 */
        MON = 1, /**//**< 星期一 */
        TUE = 2, /**//**< 星期二 */
        WED = 3, /**//**< 星期三 */
        THU = 4, /**//**< 星期四 */
        FRI = 5, /**//**< 星期五 */
        SAT = 6  /**//**< 星期六 */
}
/**//** 定义类型 TEnumDayOfWeek */
TEnumDayOfWeek; 
/**//** 定义类型 PEnumDayOfWeek */
typedef TEnumDayOfWeek* PEnumDayOfWeek;
/**//** 定义枚举变量 enum1 */
TEnumDayOfWeek enum1;       
/**//** 定义枚举指针变量 enum2 */
PEnumDayOfWeek p_enum2;
/**//*
 * @defgroup FileUtils 文件操作函数
 * @{
 */
/**//*
 * 打开文件 \n
 *  文件打开成功后,必须使用 ::CloseFile 函数关闭。
 *  @param[in] file_name 文件名字符串
 *  @param[in] file_mode 文件打开模式字符串,可以由以下几个模块组合而成:
 *  – r 读取
 *  – w 可写
 *  – a 添加
 *  – t 文本模式(不能与 b 联用)
 *  – b 二进制模式(不能与 t 联用)
 *  @return 返回文件编号
 *  – -1 表示打开文件失败
 
 *  @note 文件打开成功后,必须使用 ::CloseFile 函数关闭
 *  @par 示例:
 *  @code
    // 用文本只读方式打开文件
    int f = OpenFile("d:\\test.txt", "rt");
 *  @endcode
 
 *  @see ::ReadFile ::WriteFile ::CloseFile
 *  @deprecated 由于特殊的原因,这个函数可能会在将来的版本中取消。
 */
int OpenFile(const char* file_name, const char* file_mode);
/**//*
 * 读取文件
 *  @param[in] file 文件编号,参见:::OpenFile
 *  @param[out] buffer 用于存放读取的文件内容
 *  @param[in] len 需要读取的文件长度
 *  @return 返回读取文件的长度
 *  – -1 表示读取文件失败
 
 *  @pre \e file 变量必须使用 ::OpenFile 返回值
 *  @pre \e buffer 不能为 NULL
 *  @see ::OpenFile ::WriteFile ::CloseFile
 */
int ReadFile(int file, char* buffer, int len);
/**//*
 * 写入文件
 *  @param[in] file 文件编号,参见:::OpenFile
 *  @param[in] buffer 用于存放将要写入的文件内容
 *  @param[in] len 需要写入的文件长度
 *  @return 返回写入的长度
 *  – -1 表示写入文件失败
 
 *  @pre \e file 变量必须使用 ::OpenFile 返回值
 *  @see ::OpenFile ::ReadFile ::CloseFile
 */
int WriteFile(int file, const char* buffer, int len);
/**//*
 * 关闭文件
 *  @param file 文件编号,参见:::OpenFile
 *  @retval 0  为成功
 *  @retval -1 表示失败
 
 *  @see ::OpenFile ::WriteFile ::ReadFile
 *  @deprecated 由于特殊的原因,这个函数可能会在将来的版本中取消。
 */
int CloseFile(int file);
/**//** @}*/ // 文件操作函数
/**//** @}*/ // 自动注释文档范例
生成的chm文档截图:

概念及作用
在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据。在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问,比如程序可能需要每个线程维护一个链表,而使用相同的函数操作,最简单的办法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结构可以由Posix线程库维护,称为线程私有数据(Thread-specific Data,或TSD)。

创建和注销
Posix定义了两个API分别用来创建和注销TSD:

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))
 

该函数从TSD池中分配一项,将其值赋给key供以后访问使用。如果destr_function不为空,在线程退出(pthread_exit())时将以key所关联的数据为参数调用destr_function(),以释放分配的缓冲区。

不论哪个线程调用pthread_key_create(),所创建的key都是所有线程可访问的,但各个线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量。在LinuxThreads的实现中,TSD池用一个结构数组表示:

static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };
 

创建一个TSD就相当于将结构数组中的某一项设置为"in_use",并将其索引返回给*key,然后设置destructor函数为destr_function。

注销一个TSD采用如下API:

int pthread_key_delete(pthread_key_t key)
 

这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数(destr_function),而只是将TSD释放以供下一次调用pthread_key_create()使用。在LinuxThreads中,它还会将与之相关的线程数据项设为NULL(见"访问")。

访问
TSD的读写都通过专门的Posix Thread函数进行,其API定义如下:

int  pthread_setspecific(pthread_key_t  key,  const   void  *pointer)
void * pthread_getspecific(pthread_key_t key)

 

写入(pthread_setspecific())时,将pointer的值(不是所指的内容)与key相关联,而相应的读出函数则将与key相关联的数据读出来。数据类型都设为void *,因此可以指向任何类型的数据。

在LinuxThreads中,使用了一个位于线程描述结构(_pthread_descr_struct)中的二维void *指针数组来存放与key关联的数据,数组大小由以下几个宏来说明:

#define PTHREAD_KEY_2NDLEVEL_SIZE       32
#define PTHREAD_KEY_1STLEVEL_SIZE   \
((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE – 1)
/ PTHREAD_KEY_2NDLEVEL_SIZE)
    其中在/usr/include/bits/local_lim.h中定义了PTHREAD_KEYS_MAX为1024,因此一维数组大小为32。而具体存放的位置由key值经过以下计算得到:
idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE
idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE
 

也就是说,数据存放与一个32×32的稀疏矩阵中。同样,访问的时候也由key值经过类似计算得到数据所在位置索引,再取出其中内容返回。

使用范例
以下这个例子没有什么实际意义,只是说明如何使用,以及能够使用这一机制达到存储线程私有数据的目的。

#include <stdio.h>
#include <pthread.h>

pthread_key_t   key;

void echomsg(int t)
{
        printf("destructor excuted in thread %d,param=%d\n",pthread_self(),t);
}

void * child1(void *arg)
{
        int tid=pthread_self();
        printf("thread %d enter\n",tid);
        pthread_setspecific(key,(void *)tid);
        sleep(2);
        printf("thread %d returns %d\n",tid,pthread_getspecific(key));
        sleep(5);
}

void * child2(void *arg)
{
        int tid=pthread_self();
        printf("thread %d enter\n",tid);
        pthread_setspecific(key,(void *)tid);
        sleep(1);
        printf("thread %d returns %d\n",tid,pthread_getspecific(key));
        sleep(5);
}

int main(void)
{
        int tid1,tid2;

        printf("hello\n");
        pthread_key_create(&key,echomsg);
        pthread_create(&tid1,NULL,child1,NULL);
        pthread_create(&tid2,NULL,child2,NULL);
        sleep(10);
        pthread_key_delete(key);
        printf("main thread exit\n");
        return 0;
}
 

给例程创建两个线程分别设置同一个线程私有数据为自己的线程ID,为了检验其私有性,程序错开了两个线程私有数据的写入和读出的时间,从程序运行结果可以看出,两个线程对TSD的修改互不干扰。同时,当线程退出时,清理函数会自动执行,参数为tid。

2006年11月08日

//SvrOnlyOne.h
#ifndef _SVR_ONLY_ONE_H
#define _SVR_ONLY_ONE_H

const int SEM_PRMS = 0644;//信号量操作权限,0644即主用户(属主)可读写、组成员及其它成员可读不可写

class CSvrOnlyOne
{
public:
    bool Exists(key_t _nKey);//检查信号量是否已存在
    bool Mark(key_t _nKey);//设置信号量
    bool Unmark(key_t _nKey);//清除信号量
};

#endif //SVR_ONLY_ONE

//SvrOnlyOne.cpp
#include "ace/OS.h"
#include "ace/OS_NS_Thread.h"
#include "SvrOnlyOne.h"

bool CSvrOnlyOne::Exists(key_t _nKey)
{
    int nID = ACE_OS::semget(_nKey, 0, 0);
    if (nID == -1)
        return false;

    semun semctl_arg;
   
    semctl_arg.array = NULL;

    return ACE_OS::semctl(nID, 0, GETVAL, semctl_arg) > 0;
}

bool CSvrOnlyOne::Mark(key_t _nKey)
{
    int nID = ACE_OS::semget(_nKey, 0, 0);
    if (nID == -1)
        nID = ACE_OS::semget(_nKey, 1, IPC_CREAT | IPC_EXCL | SEM_PRMS);

    struct sembuf buf[2];

    buf[0].sem_num = 0;
    buf[0].sem_op = 0;
    buf[0].sem_flg = IPC_NOWAIT;
    buf[1].sem_num = 0;
    buf[1].sem_op = 1;
    buf[1].sem_flg = SEM_UNDO;//进程退出时自动回滚

    return ACE_OS::semop(nID, &buf[0], 2) == 0;
}

bool CSvrOnlyOne::Unmark(key_t _nKey)
{
    int nID = ACE_OS::semget(_nKey, 0, 0);
    if (nID == -1)
        return true;

    semun semctl_arg;

    semctl_arg.val = 0;

    return ACE_OS::semctl(nID, 0, IPC_RMID, semctl_arg) == 0;
}

 
  仔细想想地位卑贱的类型转换功能(cast),其在程序设计中的地位就象goto语句一样令人鄙视。但是它还不是无法令人忍受,因为当在某些紧要的关头,类型转换还是必需的,这时它是一个必需品。

  不过C风格的类型转换并不代表所有的类型转换功能。

  一来它们过于粗鲁,能允许你在任何类型之间进行转换。不过如果要进行更精确的类型转换,这会是一个优点。在这些类型转换中存在着巨大的不同,例如把一个指向const对象的指针(pointer-to-const-object)转换成指向非const对象的指针(pointer-to-non-const-object)(即一个仅仅去除const的类型转换),把一个指向基类的指针转换成指向子类的指针(即完全改变对象类型)。传统的C风格的类型转换不对上述两种转换进行区分。(这一点也不令人惊讶,因为C风格的类型转换是为C语言设计的,而不是为C++语言设计的)。

  二来C风格的类型转换在程序语句中难以识别。在语法上,类型转换由圆括号和标识符组成,而这些可以用在C++中的任何地方。这使得回答象这样一个最基本的有关类型转换的问题变得很困难:“在这个程序中是否使用了类型转换?”。这是因为人工阅读很可能忽略了类型转换的语句,而利用象grep的工具程序也不能从语句构成上区分出它们来。

  C++通过引进四个新的类型转换操作符克服了C风格类型转换的缺点,这四个操作符是, static_cast, const_cast, dynamic_cast, 和reinterpret_cast。在大多数情况下,对于这些操作符你只需要知道原来你习惯于这样写,

(type) expression

  而现在你总应该这样写:

static_cast<type>(expression)

  例如,假设你想把一个int转换成double,以便让包含int类型变量的表达式产生出浮点数值的结果。如果用C风格的类型转换,你能这样写:

int firstNumber, secondNumber;

double result = ((double)firstNumber)/secondNumber;

  如果用上述新的类型转换方法,你应该这样写:

double result = static_cast<double>(firstNumber)/secondNumber;

  这样的类型转换不论是对人工还是对程序都很容易识别。

   static_cast在功能上基本上与C风格的类型转换一样强大,含义也一样。它也有功能上限制。例如,你不能用static_cast象用C风格的类型转换一样把struct转换成int类型或者把double类型转换成指针类型,另外,static_cast不能从表达式中去除const属性,因为另一个新的类型转换操作符const_cast有这样的功能。
其它新的C++类型转换操作符被用在需要更多限制的地方。const_cast用于类型转换掉表达式的const或volatileness属性。通过使用const_cast,你向人们和编译器强调你通过类型转换想做的只是改变一些东西的constness或者volatileness属性。这个含义被编译器所约束。如果你试图使用const_cast来完成修改constness 或者volatileness属性之外的事情,你的类型转换将被拒绝。下面是一些例子:

class Widget { … };
class SpecialWidget: public Widget { … };
void update(SpecialWidget *psw);
SpecialWidget sw; // sw 是一个非const 对象。
const SpecialWidget& csw = sw; // csw 是sw的一个引用
// 它是一个const 对象
update(&csw); // 错误!不能传递一个const SpecialWidget* 变量
// 给一个处理SpecialWidget*类型变量的函数
update(const_cast<SpecialWidget*>(&csw));
// 正确,csw的const被显示地转换掉(
// csw和sw两个变量值在update
//函数中能被更新)
update((SpecialWidget*)&csw);
// 同上,但用了一个更难识别
//的C风格的类型转换
Widget *pw = new SpecialWidget;
update(pw); // 错误!pw的类型是Widget*,但是
// update函数处理的是SpecialWidget*类型
update(const_cast<SpecialWidget*>(pw));
// 错误!const_cast仅能被用在影响
// constness or volatileness的地方上。,
// 不能用在向继承子类进行类型转换。

  到目前为止,const_cast最普通的用途就是转换掉对象的const属性。

  第二种特殊的类型转换符是dynamic_cast,它被用于安全地沿着类的继承关系向下进行类型转换。这就是说,你能用dynamic_cast把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。失败的转换将返回空指针(当对指针进行类型转换时)或者抛出异常(当对引用进行类型转换时):

Widget *pw;

update(dynamic_cast<SpecialWidget*>(pw));
// 正确,传递给update函数一个指针
// 是指向变量类型为SpecialWidget的pw的指针
// 如果pw确实指向一个对象,
// 否则传递过去的将使空指针。
void updateViaRef(SpecialWidget& rsw);
updateViaRef(dynamic_cast<SpecialWidget&>(*pw));
//正确。传递给updateViaRef函数
// SpecialWidget pw 指针,如果pw
// 确实指向了某个对象
// 否则将抛出异常
dynamic_casts在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上(参见条款M24),也不能用它来转换掉constness:
int firstNumber, secondNumber;

double result = dynamic_cast<double>(firstNumber)/secondNumber;
// 错误!没有继承关系
const SpecialWidget sw;

update(dynamic_cast<SpecialWidget*>(&sw));
// 错误! dynamic_cast不能转换
// 掉const。

  如你想在没有继承关系的类型中进行转换,你可能想到static_cast。如果是为了去除const,你总得用const_cast。

  这四个类型转换符中的最后一个是reinterpret_cast。使用这个操作符的类型转换,其的转换结果几乎都是执行期定义(implementation-defined)。因此,使用reinterpret_casts的代码很难移植。

  reinterpret_casts的最普通的用途就是在函数指针类型之间进行转换。例如,假设你有一个函数指针数组:

typedef void (*FuncPtr)(); // FuncPtr is 一个指向函数
// 的指针,该函数没有参数
// 返回值类型为void
FuncPtr funcPtrArray[10]; // funcPtrArray 是一个能容纳
// 10个FuncPtrs指针的数组

  让我们假设你希望(因为某些莫名其妙的原因)把一个指向下面函数的指针存入funcPtrArray数组:

int doSomething();

  你不能不经过类型转换而直接去做,因为doSomething函数对于funcPtrArray数组来说有一个错误的类型。在FuncPtrArray数组里的函数返回值是void类型,而doSomething函数返回值是int类型。

funcPtrArray[0] = &doSomething; // 错误!类型不匹配
reinterpret_cast可以让你迫使编译器以你的方法去看待它们:
funcPtrArray[0] = // this compiles
reinterpret_cast<FuncPtr>(&doSomething);

  转换函数指针的代码是不可移植的(C++不保证所有的函数指针都被用一样的方法表示),在一些情况下这样的转换会产生不正确的结果(参见条款M31),所以你应该避免转换函数指针类型,除非你处于着背水一战和尖刀架喉的危急时刻。一把锋利的刀。一把非常锋利的刀。

  如果你使用的编译器缺乏对新的类型转换方式的支持,你可以用传统的类型转换方法代替static_cast, const_cast, 以及reinterpret_cast。也可以用下面的宏替换来模拟新的类型转换语法:

#define static_cast(TYPE,EXPR) ((TYPE)(EXPR))
#define const_cast(TYPE,EXPR) ((TYPE)(EXPR))
#define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))

  你可以象这样使用使用:

double result = static_cast(double, firstNumber)/secondNumber;
update(const_cast(SpecialWidget*, &sw));
funcPtrArray[0] = reinterpret_cast(FuncPtr, &doSomething);

  这些模拟不会象真实的操作符一样安全,但是当你的编译器可以支持新的的类型转换时,它们可以简化你把代码升级的过程。

  没有一个容易的方法来模拟dynamic_cast的操作,但是很多函数库提供了函数,安全地在派生类与基类之间进行类型转换。如果你没有这些函数而你有必须进行这样的类型转换,你也可以回到C风格的类型转换方法上,但是这样的话你将不能获知类型转换是否失败。当然,你也可以定义一个宏来模拟dynamic_cast的功能,就象模拟其它的类型转换一样:

#define dynamic_cast(TYPE,EXPR) (TYPE)(EXPR)

  请记住,这个模拟并不能完全实现dynamic_cast的功能,它没有办法知道转换是否失败。

  我知道,是的,我知道,新的类型转换操作符不是很美观而且用键盘键入也很麻烦。如果你发现它们看上去实在令人讨厌,C风格的类型转换还可以继续使用并且合法。然而,正是因为新的类型转换符缺乏美感才能使它弥补了在含义精确性和可辨认性上的缺点。并且,使用新类型转换符的程序更容易被解析(不论是对人工还是对于工具程序),它们允许编译器检测出原来不能发现的错误。这些都是放弃C风格类型转换方法的强有力的理由。还有第三个理由:也许让类型转换符不美观和键入麻烦是一件好事

 taglist:  用:TlistToggle 打开左边的tag窗口,再输一次自动关闭,按ctrl+w可以在窗口之间进行切换,也可以按照如下方法定义一个快捷键
  nnoremap <silent> <F8> :TlistToggle<CR><CR>
 tabbar:   按ESC+1,ESC+2,ESC+3, … ESC+0 在十个窗口切换,如果开了tag窗口,则可能要按一下回车,才能回到tabbar的当前编辑窗口
 SQLUtilities:这是一个用来格式化sql语言的,\sf 格式化当前行,如果sql语言占3行,则用3\sf

至此,基本工作已经完成了,你已经可以用vim来编辑一个ec文件,用ctrl+]来查找函数定义,用ctrl+t来返回,是不是很方便啊

下面列出我现在所用.vimrc,
let Tlist_Enable_Fold_Column = 0    "使taglist插件不显示左边的折叠行,
let Tlist_WinWidth = 20             "taglist窗口宽度
let Tlist_Show_One_File = 1         "taglist插件只显示当前文件的tag
let g:Tb_ForceSyntaxEnable = 1

colorscheme evening
set encoding=euc-cn                 "vim所使用的字符:euc-cn(simplified Chinese (Unix only)),如果有问题可以删除

set foldmethod=indent               "折叠使用indent风格
set foldlevel=10                    "多少层才自动折叠?[0m

set tabstop=4                       "tab占用4个字符宽度
set softtabstop=4
set shiftwidth=4
set expandtab                       "不使用tab,使用空格替代tab

set cinoptions=:N                   "使得自动缩进时swithc和case在同一列上,个人爱好,可以不设
set listchars=tab:>-,trail:-        "每个制表符会以 >— 显示1, 同时行尾空格以 – 显示, set list打开
" set whichwrap=h,l                
set nobackup                        " do not keep a backup file, use versions instead      

set cst
set csto=0
set cscopequickfix=s-,c-,d-,i-,t-,e-
cs add /u/infdev/cscope.out /u/infdev
set path+=~/include,../incl,../inc

nmap <silent> <F2>  :%s/\s\+$//<CR>         "删除行末多余空字符
nmap <silent> <F3>  [i                      "显示当前变量的定义
nmap <silent> <F4>  :TlistToggle<CR>        "打开tag窗口
nmap <silent> <F5>  gg=G                    "格式化整个文件,为误用,特意加shift键
nmap <silent> <F6>  =a{                     "格式化当前{}之内的文本
nmap <silent> <F7>  :cs find d <C-R>=expand("<cword>")<CR><CR>       "查找本函数调用的函数
nmap <silent> <F8>  :cs find c <C-R>=expand("<cword>")<CR><CR>       "查找调用指定函数的函数
nmap <silent> <F9>  :make<CR>               "make 当前目录的文件
nmap <silent> <F10> :cnext<CR>              "跳到下一个编译错误
nmap <silent> <F11> :cc<CR>                 "看完整的错误信息
nmap <silent> <F12> :clist<CR>              "看所有错误信息

下面附上常用的vim功能键

设置格式宽度
set cindent shiftwidth=4

自动排序
gg=G

    :set noexpandtab
    :%retab!
    :set expandtab
    :%retab

set autoindent shiftwidth=4
smartindent

1=3    格式化1-3行
==    格式化当前行
3==    格式化从当前行往下3行
=a{    格式化本{内的行
=a}

K 直接跳转到man说明文档

set ts=4
set sw=4

" 设置跳格距离
set expandtab
" 设置自动缩进格数
set shiftwidth=4
" 打开自动缩进功能
set smartindent

set ts=4
set expandtab
set autoindent

//替换
 :s/^the/these/
:54s/President/Fool/
:1,5s/this/that/g
:.,$s/yes/no/

:%s/Professor/Teacher/c
:%s/Professor/Teacher/g

//书签
mx , x可以是任何字母
`x,或到书签位置
‘x,到书签首位置

//移动
%到匹配的{/}

#下一个匹配的单词

*上一个