网络服务器软件开发/中间件开发,关注ACE/ICE/boost

C++博客 首页 新随笔 联系 聚合 管理
  152 Posts :: 3 Stories :: 172 Comments :: 0 Trackbacks

#

Base64编码其实是将3个8位字节转换为4个6位字节,( 3*8 = 4*6 = 24 ) 这4个六位字节
其实仍然是8位,只不过高两位被设置为0. 当一个字节只有6位有效时,它的取值空间为0
到 2的6次方减1 即63,也就是说被转换的Base64编码的每一个编码的取值空间为(0~63)

事实上,0~63之间的ASCII码有许多不可见字符,所以应该再做一个映射,映射表为
‘A‘ ~ ‘Z‘ ? ASCII(0 ~ 25)
‘a’ ~ ‘z‘ ? ASCII(26 ~ 51)
‘0’ ~ ‘9‘ ? ASCII(52 ~ 61)
‘+‘ ? ASCII(62)
‘/‘ ? ASCII(63)
这样就可以将3个8位字节,转换为4个可见字符。
具体的字节拆分方法为:(图(画得不好,领会精神 :-))
aaaaaabb ccccdddd eeffffff
~~~~~~~~ ~~~~~~~~ ~~~~~~~~
字节 1 字节 2 字节 3
||
\/
00aaaaaa 00bbcccc 00ddddee 00ffffff

注:上面的三个字节位原文,下面四个字节为Base64编码,其前两位均为0。
这样拆分的时候,原文的字节数量应该是3的倍数,当这个条件不能满足时,用全零字节
补足,转化时Base64编码用=号代替,这就是为什么有些Base64编码以一个或两个等号结
束的原因,但等号最多有两个,因为:如果F(origin)代表原文的字节数,F(remain)代
表余数,则
F(remain) = F(origin) MOD 3 成立。
所以F(remain)的可能取值为0,1,2.
如果设 n = [F(origin) – F(remain)] / 3
当F(remain) = 0 时,恰好转换为4*n个字节的Base64编码。
当F(remain) = 1 时,由于一个原文字节可以拆分为属于两个Base64编码的字节,为了
让Base64编码是4的倍数,所以应该为补2个等号。
当F(remain) = 2 时,由于两个原文字节可以拆分为属于3个Base64编码的字节,同理,
应该补上一个等号 


posted @ 2007-04-05 16:43 true 阅读(632) | 评论 (0)编辑 收藏

一、      TinyXml的特点

TinyXml是一个基于DOM模型的、非验证的轻量级C++解释器。

1.      SAX和DOM

目前XML的解析主要有两大模型:SAX和DOM。

其中SAX是基于事件的,其基本工作流程是分析XML文档,当发现了一个新的元素时,产生一个对应事件,并调用相应的用户处理函数。这种方式占用内存少,速度快,但用户程序相应得会比较复杂。

而DOM(文档对象模型),则是在分析时,一次性的将整个XML文档进行分析,并在内存中形成对应的树结构,同时,向用户提供一系列的接口来访问和编辑该树结构。这种方式占用内存大,速度往往慢于SAX,但可以给用户提供一个面向对象的访问接口,对用户更为友好。

 

2.      验证和非验证

对于一个特定的XML文档而言,其正确性分为两个层次。首先是其格式应该符合XML的基本格式要求,比如第一行要有声明,标签的嵌套层次必须前后一致等等,符合这些要求的文件,就是一个合格的XML文件,称作well-formatted。但除此之外,一个XML文档因其内容的不同还必须在语义上符合相应的标准,这些标准由相应的DTD文件或者Schema文件来定义,符合了这些定义要求的XML文件,称作valid。

因此,解析器也分为两种,一种是验证的,即会跟据XML文件中的声明,用相应的DTD文件对XML文件进行校验,检查它是否满足DTD文件的要求。另一种是忽略DTD文件,只要基本格式正确,就可以进行解析。

就我所知,验证的解析器通常都是比较重量级的。TinyXml不支持验证,但是体积很小,用在解析格式较为简单的XML文件,比如配置文件时,特别的合适。

 

二、 TinyXml的构建和使用
1.      获取

TinyXml首页在http://www.grinninglizard.com/tinyxml/index.html,从这里可以找到最新版本的源代码,目前的版本是2.3.4。

2.构建

TinyXml在构建时可以选择是否支持STL,选择的话,则可以使用std::string,所以通常应该打开这个选项。

在Windows上,TinyXml的源码包里提供了VC6的工程文件,直接用它就可以生成两个静态库(带STL和不带STL),非常容易。唯一需要注意的是,默认生成的库是单线程的,如果用在多线程的项目中,需要改动一下配置,生成相应的多线程库。

在Unix平台上,TinyXml的源码包里只提供了一个Makefile,对于典型的Linux系统,或装了gcc和gmake的其他Unix,这个Makefile足够用了,我在RH9和RHEL4上测试,简单的make就成功了。需要注意的有以下几点:默认的编译是不支持STL的,可以通过编辑Makefile的TINYXML_USE_STL := NO那一行,把NO改成YES就可以支持STL了;还有默认只生成了一个测试程序,没有生成任何库,如果要生成静态库的话,可以用ar命令,将生成的几个目标文件打包就行了,如果要生成动态库,则需要加上-fpic参数重新编译。

3.      使用

构建了相应的库之后,在使用了它们的工程中,只要在连接时把他们连上就行了。需要注意的是,如果需要STL支持,在编译用到了TinyXml的文件时,需要定义一个宏TIXML_USE_STL,对gcc,可以使用参数-DTIXML_USE_STL,对cl.exe(VC),可以使用参数/DTIXML_USE_STL,如果嫌麻烦,可以直接定义在 tinyxml.h文件里。

 

三、 TinyXml的编程模型1.      类之间的关系

TinyXml实现的时DOM访问模型,因此提供了一系列的类对应XML文件中的各个节点。主要类间的关系如下图所示:

 

 

TiXmlBase:其他类的基类,是个抽象类

TiXmlNode:表示一个节点,包含节点的一般方法,如访问自节点、兄弟节点、编辑自身、编辑子节电

TiXmlDocument:表示整个XML文档,不对应其中某个特定的节点。

TiXmlElement:表示元素节点,可以包含子节点和TiXmlAttribute

TiXmlComment:表示注释

TiXmlDeclaration:表示声明

TiXmlText:表示文本节点

TiXmlUnknown:表示未知节点,通常是出错了

TiXmlAttribute:表示一个元素的属性

下面是一个简单的例子:

<?xml version="1.0" encoding="utf-8" ?>

 

 

<!-This is only a sample-->

 

 

<book>

 

 

       <name>TinyXml How To</name>

 

 

       <price unit=”RMB”>20</price>

 

 

       <description>Some words…</description>

 

 

</ book >

 

 

整个文档,对应TiXmlDocument

book,name,price, description,都对应TiXmlElement

第一行对应一个TiXmlDeclaration

第二行对应一个TiXmlComment

“TinyXml How To”对应一个TiXmlText

unit则是price的一个TiXmlAttribute

这些类与XML文件中的相应元素都有很好的对应关系,因此相信参照TinyXml的文档,可以很容易的掌握各个方法的使用。

 

2.  需要注意的问题

各类之间的转换

 

 

由于各个节点类都从TiXmlNode继承,在使用时常常需要将TiXmlNode*类型的指针转换为其派生类的指针,在进行这种转换时,应该首先使用由TiXmlNode类提供的一系列转换函数,如ToElement(void),而不是c++的dynamic_cast

 

检查返回值

 

 

由于TinyXml是一个非校验的解析器,因此当解析一个文件时,很可能文件并不包含我们预期的某个节点,在这种情况下,TinyXml将返回空指针。因此,必须要对返回值进行检查,否则将很容易出现内存访问的错误。

 

如何重头建立一个XML文件

 

 

先建立一个TiXmlDocument对象,然后,载入某个模板,或者直接插入一个节点作为根节点,接着就可以像打开一个已有的XML文件那样对它进行操作了。

 

四、总结

TinyXml最大的特点就是它很小,可以很方便的静态连接到程序里。对于像配置文件、简单的数据文件这类文件的解析,它很适合。但是由于它是非验证的,因此需要在程序里做许多检查工做,加重了程序编写的负担。因此对于复杂的XML文件,我觉得最好还是用验证的解析器来处理。
 



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1006509


posted @ 2007-04-05 14:10 true 阅读(567) | 评论 (1)编辑 收藏

通用Makefile及部分解释

######################################
# Copyright (c) 1997 George Foot (george.foot@merton.ox.ac.uk)
# All rights reserved.
######################################
#目标(可执行文档)名称,库(譬如stdcx,iostr,mysql等),头文件路径
DESTINATION := test
LIBS :=
INCLUDES := .


RM := rm -f
#C,CC或CPP文件的后缀
PS=cpp
# GNU Make的隐含变量定义
CC=g++
CPPFLAGS = -g -Wall -O3 -march=i486
CPPFLAGS += $(addprefix -I,$(INCLUDES))
CPPFLAGS += -MMD

#以下部分无需修改
SOURCE := $(wildcard *.$(PS))
OBJS := $(patsubst %.$(PS),%.o,$(SOURCE))
DEPS := $(patsubst %.o,%.d,$(OBJS))
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.$(PS),$(MISSING_DEPS)))

.PHONY : all deps objs clean rebuild

all : $(DESTINATION)

deps : $(DEPS)
        $(CC) -MM -MMD $(SOURCE)


objs : $(OBJS)

clean :
        @$(RM) *.o
        @$(RM) *.d
        @$(RM) $(DESTINATION)

rebuild: clean all

ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS) :
        @$(RM) $(patsubst %.d,%.o,$@)
endif

-include $(DEPS)

$(DESTINATION) : $(OBJS)
        $(CC) -o $(DESTINATION) $(OBJS) $(addprefix -l,$(LIBS))
#结束

  • 原作者是Gorge Foot,写这个Makefile的时候还是一个学生
  • ":="赋值,和"="不同的是,":="在赋值的同时,会将赋值语句中所有的变量就地展开,也就是说,A:=$(B)后,B的值的改变不再影响A
  • 隐含规则。GUN Make在不特别指定的情况下会使用诸如以下编译命令:$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@,这也是为什么这个Makefile最后一个命令没有添加$(CPPFLAGS)的原因,因为缺省是包含这个变量的
  • 函数和变量很相似:"$ (函数名,空格,一列由逗号分隔的参数)"
  • SOURCES = $(wildcard *.cpp) 列出工作目录下文件名满足"*.cpp"条件的文件,以空格分隔,并将列表赋给SOURCE变量
  • patsubst函数:3个参数。功能是将第三个参数中的每一项(由空格分隔)符合第一个参数描述的部分替换成第二个参数制定的值
  • addprefix函数:2个参数。将源串(第2个参数,由空格分隔)中的每一项添加前缀(第1个参数)
  • filter-out函数:2个参数。从第二串中过滤掉包含在第一个串中的项
  • $(CC) -MM -MMD $(SOURCE) : 对每个源文件生成依赖(dependence,Make通过依赖规则来判断是否需要重新编译某个文件),"D"生成".d"文件,-MM表示去掉 depends里面的系统的头文件(使用<>包含的头文件)(若使用-M则全部包含,事实上,系统头文件被修改的可能性极小,不需要执行依赖检查)
  • .PHONY,不检查后面制定各项是否存在同名文件
  • ifneg...else...endif,Makefile中的条件语句
  • -include $(DEPS) : 将DEPS中的文件包含进来,"-"表示忽略文件不存在的错误
  • @$(RM) *.o : 开头的"@"表示在Make的时候,不显示这条命令(GNU Make缺省是显示的)
  • all : 作为第一个出现的目标项目,Make会将它作为主要和缺省项目("make"就表示"make all")
  • deps : 只生成依赖文件(.d文件)
  • objs : 为每一个源码程序生成或更新 '.d' 文件和'.o'文件
  • clean : 删除所有'.d','.o'和可执行文件
  • rebuild : clean然后重建
  • 内部变量$@, $< $^ : 分别表示目标名(:前面的部分,比如all),依靠列表(:后面的部分)中的第一个依靠文件,所有依靠文件

posted @ 2007-04-05 12:01 true 阅读(440) | 评论 (0)编辑 收藏

 
  · 闫健勇·CPCW
  
    当前,虽然Linux还不很普及,在Linux下编写和编译程序的人不多。但是我相信,随着Linux性能的不断提升和逐渐普及,会有许多自由软件出现,也会有许多人成为Linux下的程序员。我结合自己的经验,介绍一下Linux下编写和编译程序所要注意的几个问题,奉献给希望为Linux的发展作出贡献的人们。
  
  
  Linux下怎样编译程序?
  
  大多数Linux程序都是由C语言编写的并由GNU C编译而成。现在GCC是各种发行套件的一部分。有关最新GCC编译器的版本、文章和补丁请看ftp://ftp.gnu.org/pub/gnu/。
  
  由C++编写的程序必须由GNU C++编译,GNU C++也是各种发行套件的一部分,在以上网址也有最新版本和补丁。
  
  编译2.0.x的内核需要2.7.2.x版本的GCC,如用象GCC 2.8.x, EGCS, or PGCC别的编译器编译内核可能产生不可预想的后果。
  
  
  怎样移植其它Unix程序到Linux上?
  
  总得来说,Unix上的程序不需要做改动,只要简单的按照提示就可以移植到Linux上,如果安装过程中出现错误信息,而你又不知道怎么处理,你可以猜或略去,不过这样产生的程序往往带有bug。所以最好还是问一下有经验的人。
  
  如果要从BSD-ish移植程序,试一试在编译时加上-I/usr/include/bsd 和 ?lbsd命令。
  
  
  什么是ld.so,从哪可以找到它?
  
  ld.so是动态函数库装载器。过去,使用共享函数库的程序在原代码开头使用约3K的空间来寻找和加载共享函数库,现在,这段代码被加进了一个特殊共享函数库/lib/ld.so,所有的程序都可以使用该共享库,这样就节省了磁盘空间,而且升级方便。
  
  ld.so可以从以下网址得到tsx-11.mit.edu/pub/linux/packages/GCC/。
  
  
  怎样升级库函数而不使系统崩溃?
  
  注意:进行此操作应该养成做备份的习惯,因为这项操作很容易出错。
  
  如果你升级象libc4这样的老函数库,这个过程会变得非常困难。而且你应该在该系统上让libc4和libc5共存,因为,有些老程序还需要它。升级libc5也一样。
  
  升级动态库的问题常出现在当你移走老的函数库时,用来升级的程序也运行不了了。有许多方法可以解决这个问题。一个方法就是暂时备份一下运行程序所需函数库,它们一般在/lib/、/usr/lib/、 /usr/local/lib/、或其它的地方,在文件/etc/ld.so.conf中都有详细记录。
  
  例如,当你升级libc5时,目录/lib/中有如下文件
  
  libc.so.5
  
  libc.so.5.4.33
  
  libm.so.5
  
  libm.so.5.0.9
  
  这些是C函数库和数学库,拷贝它们到文件/etc/ld.so.conf中含有的其它的目录,如/usr/lib/中:
  
  cp -df /lib/libc.so.5* /usr/lib/
  
  cp -df /lib/libm.so.5* /usr/lib/
  
  ldconfig
  
  一定要记住运行ldconfig来升级函数库的配置文件。
  
  文件libc.so.5 和 libm.so.5是实际库文件的链接文件,当你升级的时候,如果老的链接文件存在,新的链接不会产生,除非你使用CP命令的-f选项。CP的-d选项只复制链接文件,不复制原文件。
  
  如果你需要直接覆盖链接,使用ln命令的选项-f。
  
  例如,拷贝新的库函数覆盖旧的。先对新的函数库做一个链接,然后把函数库和链接一起拷贝到/lib/中,命令如下:
  
  ln -sf ./libm.so.5.0.48 libm.so.5
  
  ln -sf ./libc.so.5.0.48 libc.so.5
  
  cp -df libm.so.5* /lib
  
  cp -df libc.so.5* /lib
  
  重申一下,拷贝完别忘记运行ldconfig.
  
  如果一切工作顺利的话,你可以删除老的函数库的备份。
  
  
  我能否把在486上编译的代码或编译器拿到386上用?
  
  当然,除非你编译的是内核。
  
  GCC用来在486上编译的选项-m486 只是优化了所编译程序,使其运行快一些。这些编译的程序仍能很好的在386上运行,只是效果差一些。
  
  然而,从内核1.3.35以后,采用486或Pentium选项编译的内核不能用于386的机器。
  
  GCC可以针对386和486进行配置。两者区别在于,针对386配置的GCC把-m386作为缺省选项,而针对486配置的GCC把-m486作为缺省选项,仅此而已。
  
  
  gcc -O6可以干什么?
  
  目前,它和 -O2 (GCC 2.5) 或 -O3 (GCC 2.6, 2.7)功能一样,所有版本大于它的功能也一样。新版内核的Makefiles使用-O2,所以你也应该用-O2。
  
  
  linux/*.h 和asm/*.h在什么地方?
  
  目录 /usr/include/linux/ 和 /usr/include/asm/低下的文件是内核头文件的软链接,内核头文件其实在目录/usr/src/kernel*/低下。
  
  
  怎样作一个共享函数库?
  
  对ELF, 命令如下:
  
  gcc -fPIC -c *.c
  
  gcc -shared -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0 *.o
  
  对a.out,从 http://tsx-11.mit.edu/pub/linux/packages/GCC/src/ 下载n.nn.tar.gz,其中包含详细说明。建议你将共享库由a.out升级为ELF。
  
  
  为什么我编译的可执行程序非常大?
  
  用ELF编译器,生成可执行程序太大最可能的原因是没有合适的.so库与你使用的库函数链接。对每一个象libc.so.5.2.18的函数库,应该有一个象libc.so的链接。
  
  用a.out编译器,生成可执行程序太大可能是使用了-g选项,这会生成静态链接库而不是动态链接库。
  
  
  从哪可以得到对于Linux的‘lint’?
  
  大部分‘lint’的功能已经内置进了GCC,打开GCC的-Wall选项会打开许多有用的外部警告。
  
  还有一个叫`lclint'的软件功能和传统的lint差不多,原代码可以在http://larch.lcs.mit.edu /pub/Larch/lclint/中找到。
    做人要厚道,请注明转自酷网动力(www.ASPCOOL.COM)。
posted @ 2007-04-05 10:34 true 阅读(331) | 评论 (0)编辑 收藏

摘要


编写连接数巨大的高负载服务器程序时,经典的多线程模式和select模式都不再适用。
应当抛弃它们,采用epoll/kqueue/dev_poll来捕获I/O事件。最后简要介绍了AIO。


由来


网络服务在处理数以万计的客户端连接时,往往出现效率低下甚至完全瘫痪,这被称为
C10K问题。随着互联网的迅速发展,越来越多的网络服务开始面临C10K问题,作为大型
网站的开发人员有必要对C10K问题有一定的了解。本文的主要参考文献是
<http://www.kegel.com/c10k.htmlhttp://www.kegel.com/c10k.htmls

C10K问题的最大特点是:设计不够良好的程序,其性能和连接数及机器性能的关系往往
是非线性的。举个例子:如果没有考虑过C10K问题,一个经典的基于select的程序能在
旧服务器上很好处理1000并发的吞吐量,它在2倍性能新服务器上往往处理不了并发
2000的吞吐量。

这是因为在策略不当时,大量操作的消耗和当前连接数n成线性相关。会导致单个任务
的资源消耗和当前连接数的关系会是O(n)。而服务程序需要同时对数以万计的socket进
行I/O处理,积累下来的资源消耗会相当可观,这显然会导致系统吞吐量不能和机器性
能匹配。为解决这个问题,必须改变对连接提供服务的策略。


基本策略


主要有两方面的策略:1.应用软件以何种方式和操作系统合作,获取I/O事件并调度多
个socket上的I/O操作;2. 应用软件以何种方式处理任务和线程/进程的关系。前者主
要有阻塞I/O、非阻塞I/O、异步I/O这3种方案,后者主要有每任务1进程、每任务1线
程、单线程、多任务共享线程池以及一些更复杂的变种方案。常用的经典策略如下:

1.         Serve one client with each thread/process, and use blocking I/O
这是小程序和java常用的策略,对于交互式的长连接应用也是常见的选择(比如BBS)。
这种策略很能难足高性能程序的需求,好处是实现极其简单,容易嵌入复杂的交互逻
辑。Apache、ftpd等都是这种工作模式。

2.         Serve many clients with single thread, and use nonblocking I/O
and readiness notification
这是经典模型,datapipe等程序都是如此实现的。优点在于实现较简单,方便移植,也
能提供足够的性能;缺点在于无法充分利用多CPU的机器。尤其是程序本身没有复杂的
业务逻辑时。

3.         Serve many clients with each thread, and use nonblocking I/O and
readiness notification
对经典模型2的简单改进,缺点是容易在多线程并发上出bug,甚至某些OS不支持多线程
操作readiness notification。

4.         Serve many clients with each thread, and use asynchronous I/O
在有AI/O支持的OS上,能提供相当高的性能。不过AI/O编程模型和经典模型差别相当
大,基本上很难写出一个框架同时支持AI/O和经典模型,降低了程序的可移植性。在
Windows上,这基本上是唯一的可选方案。

本文主要讨论模型2的细节,也就是在模型2下应用软件如何处理Socket I/O。


select 与 poll


最原始的同步阻塞 I/O 模型的典型流程如下:

同步阻塞 I/O 模型的典型流程

从应用程序的角度来说,read 调用会延续很长时间,应用程序需要相当多线程来解决
并发访问问题。同步非阻塞I/O对此有所改进:

经典的单线程服务器程序结构往往如下:


do {

         Get Readiness Notification of all sockets

         Dispatch ready handles to corresponding handlers

                   If (readable) {
                            read the socket

                            If (read done)

                                     Handler process the request
                   }

                   if (writable)

                            write response

                   if (nothing to do)

                            close socket

} while(True)

非阻塞 I/O 模型的典型流程:

异步阻塞 I/O 模型的典型流程

其中关键的部分是readiness notification,找出哪一个socket上面发生了I/O事件。
一般从教科书和例子程序中首先学到的是用select来实现。Select定义如下:


int select(int n, fd_set *rd_fds, fd_set *wr_fds, fd_set *ex_fds, struct
timeval *timeout);

Select用到了fd_set结构,从man page里可以知道fd_set能容纳的句柄和FD_SETSIZE相
关。实际上fd_set在*nix下是一个bit标志数组,每个bit表示对应下标的fd是不是在
fd_set中。fd_set只能容纳编号小于 FD_SETSIZE的那些句柄。

FD_SETSIZE默认是1024,如果向fd_set里放入过大的句柄,数组越界以后程序就会垮
掉。系统默认限制了一个进程最大的句柄号不超过1024,但是可以通过ulimit -n命令
/setrlimit函数来扩大这一限制。如果不幸一个程序在FD_SETSIZE=1024的环境下编
译,运行时又遇到ulimit –n > 1024的,那就只有祈求上帝保佑不会垮掉了。


在ACE环境中,ACE_Select_Reactor针对这一点特别作了保护措施,但是还是有recv_n
这样的函数间接的使用了select,这需要大家注意。

针对fd_set的问题,*nix提供了poll函数作为select的一个替代品。Poll的接口如下:


int poll(struct pollfd *ufds, unsigned int nfds, int timeout);

第1个参数ufds是用户提供的一个pollfd数组,数组大小由用户自行决定,因此避免了
FD_SETSIZE带来的麻烦。Ufds是fd_set的一个完全替代品,从select到poll的移植很方
便。到此为止,至少我们面对C10K,可以写出一个能work的程序了。

然而Select和Poll在连接数增加时,性能急剧下降。这有两方面的原因:首先操作系统
面对每次的select/poll操作,都需要重新建立一个当前线程的关心事件列表,并把线
程挂在这个复杂的等待队列上,这是相当耗时的。其次,应用软件在select/poll返回
后也需要对传入的句柄列表做一次扫描来dispatch,这也是很耗时的。这两件事都是和
并发数相关,而I/O事件的密度也和并发数相关,导致CPU占用率和并发数近似成O(n2)
的关系。


epoll, kqueue, /dev/poll


因为以上的原因,*nix的hacker们开发了epoll, kqueue, /dev/poll这3套利器来帮助
大家,让我们跪拜三分钟来感谢这些大神。其中epoll是linux的方案,kqueue是
freebsd的方案,/dev/poll是最古老的Solaris的方案,使用难度依次递增。

简单的说,这些api做了两件事:1.避免了每次调用select/poll时kernel分析参数建立
事件等待结构的开销,kernel维护一个长期的事件关注列表,应用程序通过句柄修改这
个列表和捕获I/O事件。2.避免了select/poll返回后,应用程序扫描整个句柄表的开
销,Kernel直接返回具体的事件列表给应用程序。

在接触具体api之前,先了解一下边缘触发(edge trigger)和条件触发(level trigger)
的概念。边缘触发是指每当状态变化时发生一个io事件,条件触发是只要满足条件就发
生一个io事件。举个读socket的例子,假定经过长时间的沉默后,现在来了100个字
节,这时无论边缘触发和条件触发都会产生一个read ready notification通知应用程
序可读。应用程序读了50个字节,然后重新调用api等待io事件。这时条件触发的api会
因为还有50个字节可读从而立即返回用户一个read ready notification。而边缘触发
的api会因为可读这个状态没有发生变化而陷入长期等待。

因此在使用边缘触发的api时,要注意每次都要读到socket返回EWOULDBLOCK为止,否则
这个socket就算废了。而使用条件触发的api时,如果应用程序不需要写就不要关注
socket可写的事件,否则就会无限次的立即返回一个write ready notification。大家
常用的select就是属于条件触发这一类,以前本人就犯过长期关注socket写事件从而
CPU 100%的毛病。

epoll的相关调用如下:


int epoll_create(int size)


int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)


int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int
timeout)

epoll_create创建kernel中的关注事件表,相当于创建fd_set。

epoll_ctl修改这个表,相当于FD_SET等操作

epoll_wait等待I/O事件发生,相当于select/poll函数

epoll完全是select/poll的升级版,支持的事件完全一致。并且epoll同时支持边缘触
发和条件触发,一般来讲边缘触发的性能要好一些。这里有个简单的例子:


struct epoll_event ev, *events;

int kdpfd = epoll_create(100);

ev.events = EPOLLIN | EPOLLET;  // 注意这个EPOLLET,指定了边缘触发

ev.data.fd =listener;

epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev);

for(;;) {

   nfds = epoll_wait(kdpfd, events, maxevents, -1);

 

   for(n = 0; n < nfds; ++n) {

       if(events[n].data.fd == listener) {

           client = accept(listener, (struct sockaddr *) &local,

                           &addrlen);

           if(client < 0){

               perror("accept");

               continue;

           }

           setnonblocking(client);

           ev.events = EPOLLIN | EPOLLET;

           ev.data.fd = client;

           if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {

               fprintf(stderr, "epoll set insertion error: fd=%d0,

                       client);

               return -1;

           }

       }

       else

           do_use_fd(events[n].data.fd);

   }

}

简单介绍一下kqueue和/dev/poll

kqueue是freebsd的宠儿,kqueue实际上是一个功能相当丰富的kernel事件队列,它不
仅仅是select/poll的升级,而且可以处理signal、目录结构变化、进程等多种事件。
Kqueue是边缘触发的

/dev/poll是Solaris的产物,是这一系列高性能API中最早出现的。Kernel提供一个特
殊的设备文件/dev/poll。应用程序打开这个文件得到操纵fd_set的句柄,通过写入
pollfd来修改它,一个特殊ioctl调用用来替换select。由于出现的年代比较早,所以
/dev/poll的接口现在看上去比较笨拙可笑。

C++开发:ACE 5.5以上版本提供了ACE_Dev_Poll_Reactor封装了epoll和/dev/poll两种
api,需要分别在config.h中定义ACE_HAS_EPOLL和ACE_HAS_DEV_POLL来启用。

Java开发: JDK 1.6的Selector提供了对epoll的支持,JDK1.4提供了对/dev/poll的支
持。只要选择足够高的JDK版本就行了。


异步I/O以及Windows


和经典模型不同,异步I/O提供了另一种思路。和传统的同步I/O不同,异步I/O允许进
程发起很多 I/O 操作,而不用阻塞或等待任何操作完成。稍后或在接收到 I/O 操作完
成的通知时,进程就可以检索 I/O 操作的结果。

异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会立即返回,说明
read 请求已经成功发起了。在后台完成读操作时,应用程序然后会执行其他处理操
作。当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成
这次 I/O 处理过程。异步I/O 模型的典型流程:

异步非阻塞 I/O 模型的典型流程

对于文件操作而言,AIO有一个附带的好处:应用程序将多个细碎的磁盘请求并发的提
交给操作系统后,操作系统有机会对这些请求进行合并和重新排序,这对同步调用而言
是不可能的——除非创建和请求数目同样多的线程。

Linux Kernel 2.6提供了对AIO的有限支持——仅支持文件系统。libc也许能通过来线
程来模拟socket的AIO,不过这对性能没意义。总的来说Linux的aio还不成熟

Windows对AIO的支持很好,有IOCP队列和IPCP回调两种方式,甚至提供了用户级异步调
用APC功能。Windows下AIO是唯一可用的高性能方案,详情请参考MSDN 

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1537545


posted @ 2007-04-05 09:00 true 阅读(1217) | 评论 (0)编辑 收藏

在ACE中使用epoll
2007-01-18 15:56

星期四, 六月 8th, 2006 

        很显然,文章的标题决定了我们是在linux下使用ACE。我们知道ACE在linux下缺省是用select来实现Reactor的,epoll相对于select的好处这里就不再啰嗦了,我们直接讲操作步骤:
    第一:重新编译ACE库
     ACE库中通过ACE_Dev_Poll_Reactor类来支持epoll,但是ACE库缺省的安装是没有编译这个类的,我们要做的就是将ACE_Dev_Poll_Reactor编译连接到ACE库中(faint,又要重新编译ACE,在我那台破服务器上编译一次需要一个多小时).我的操作系统是Redhat linux AS4.0,ACE的版本是5.4.10。根据ACE压缩包中的ACE-INSTALL.html,我是用”Building ACE with GNU Autoconf“这种方式来安装的,安装步骤如下(很简单,就不翻译了):
       1 cd to the top-level ACE_wrappers directory.

       2.Create a subdirectory to hold your build’s configuration and built ACE version,     and   then change to the new directory:

       mkdir build

       cd build

     

       3.Note that you do not run the create_ace_build.pl utility mentioned in the Cloning the Source Tree section. The configure script takes care of creating all files and links that are needed.

Configure ACE for your platform by issuing the following command: c

       ../configure [options]

     
      4.Build ACE by typing make.

      5. Install ACE by typing make install.
      好,现在终于可以讲如何将ACE_Dev_Poll_Reactor编译到ACE库中去了。在上述的第一步和第二步之间修改ACE_wrappers/ace/config-linux.h,增加一行:#define ACE_HAS_EVENT_POLL,然后执行第2、3步,第3步../configure执行完之后,build目录下会生成一些文件和目录,打开ACE_wrappers/build/ace/config.h,增加一行:#define ACE_HAS_EVENT_POLL。然后执行第4步make和第5步make install.OK,在漫长的编译以后,支持epoll的ACE库总算完成了。

     第二:修改应用程序
        应用程序修改很简单,两行代码搞掂,在应用程序初始化时(必须是在第一次使用ACE_Reactor::instance()之间)加入:
       
        m_pDevPollReactor=new ACE_Dev_Poll_Reactor;
       ACE_Reactor::instance(new ACE_Reactor(m_pDevPollReactor));
      
       那么在后续的对ACE_Reactor::instance()的调用就是使用ACE_Dev_Poll_Reactor的实现了。
  
   第三:重新编译应用程序
  
        在应用程序的makefile中加入 -DACE_HAS_EVENT_POLL,重新make应用程序。OK,打完收工。
        

posted @ 2007-04-05 08:32 true 阅读(2132) | 评论 (0)编辑 收藏

我们通常把一些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。本文主要通过举例来说明在Linux中如何创建静态库和动态库,以及使用它们。

在创建函数库前,我们先来准备举例用的源程序,并将函数库的源程序编译成.o文件。

第1步:编辑得到举例的程序--hello.h、hello.c和main.c;

hello.c(见程序2)是函数库的源程序,其中包含公用函数hello,该函数将在屏幕上输出"Hello XXX!"。hello.h(见程序1)为该函数库的头文件。main.c(见程序3)为测试库文件的主程序,在主程序中调用了公用函数hello。

 #ifndef HELLO_H
 #define HELLO_H
 
 void hello(const char *name);
 
 #endif //HELLO_H
 程序1: hello.h
 
 #include <stdio.h>
 
 void hello(const char *name)
 {
  printf("Hello %s!\n", name);
 }
 程序2: hello.c
 
 #include "hello.h"
 
 int main()
 {
  hello("everyone");
  return 0;
 }
 程序3: main.c

第2步:将hello.c编译成.o文件;

无论静态库,还是动态库,都是由.o文件创建的。因此,我们必须将源程序hello.c通过gcc先编译成.o文件。

在系统提示符下键入以下命令得到hello.o文件。

# gcc -c hello.c

#

(注1:本文不介绍各命令使用和其参数功能,若希望详细了解它们,请参考其他文档。)

(注2:首字符"#"是系统提示符,不需要键入,下文相同。)

我们运行ls命令看看是否生存了hello.o文件。

# ls

hello.c hello.h hello.o main.c

#

(注3:首字符不是"#"为系统运行结果,下文相同。)

在ls命令结果中,我们看到了hello.o文件,本步操作完成。

下面我们先来看看如何创建静态库,以及使用它。

第3步:由.o文件创建静态库;

静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为myhello,则静态库文件名就是libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用ar命令。

在系统提示符下键入以下命令将创建静态库文件libmyhello.a。

# ar cr libmyhello.a hello.o

#

我们同样运行ls命令查看结果:

# ls

hello.c hello.h hello.o libmyhello.a main.c

#

ls命令结果中有libmyhello.a。

第4步:在程序中使用静态库;

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件。

在程序3:main.c中,我们包含了静态库的头文件hello.h,然后在主程序main中直接调用公用函数hello。下面先生成目标程序hello,然后运行hello程序看看结果如何。

# gcc -o hello main.c -L. -lmyhello

# ./hello

Hello everyone!

#

我们删除静态库文件试试公用函数hello是否真的连接到目标文件 hello中了。

# rm libmyhello.a

rm: remove regular file `libmyhello.a'? y

# ./hello

Hello everyone!

#

程序照常运行,静态库中的公用函数已经连接到目标文件中了。

我们继续看看如何在Linux中创建动态库。我们还是从.o文件开始。

第5步:由.o文件创建动态库文件;

动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyhello.so。用gcc来创建动态库。

在系统提示符下键入以下命令得到动态库文件libmyhello.so。

# gcc -shared -fPCI -o libmyhello.so hello.o

#

我们照样使用ls命令看看动态库文件是否生成。

# ls

hello.c hello.h hello.o libmyhello.so main.c

#

第6步:在程序中使用动态库;

在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。我们先运行gcc命令生成目标文件,再运行它看看结果。

# gcc -o hello main.c -L. -lmyhello

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory

#

哦!出错了。快看看错误提示,原来是找不到动态库文件libmyhello.so。程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件libmyhello.so复制到目录/usr/lib中,再试试。

# mv libmyhello.so /usr/lib

# ./hello

Hello everyone!

#

成功了。这也进一步说明了动态库在程序运行时是需要的。

我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的gcc命令完全一样,那当静态库和动态库同名时,gcc命令会使用哪个库文件呢?抱着对问题必究到底的心情,来试试看。

先删除除.c和.h外的所有文件,恢复成我们刚刚编辑完举例程序状态。

# rm -f hello hello.o /usr/lib/libmyhello.so

# ls

hello.c hello.h main.c

#

在来创建静态库文件libmyhello.a和动态库文件libmyhello.so。

# gcc -c hello.c

# ar cr libmyhello.a hello.o

# gcc -shared -fPCI -o libmyhello.so hello.o

# ls

hello.c hello.h hello.o libmyhello.a libmyhello.so main.c

#

通过上述最后一条ls命令,可以发现静态库文件libmyhello.a和动态库文件libmyhello.so都已经生成,并都在当前目录中。然后,我们运行gcc命令来使用函数库myhello生成目标文件hello,并运行程序 hello。

# gcc -o hello main.c -L. -lmyhello

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory

#

从程序hello运行的结果中很容易知道,当静态库和动态库同名时, gcc命令将优先使用动态库。

posted @ 2007-04-04 18:22 true 阅读(729) | 评论 (0)编辑 收藏

     摘要: TinyXml是一个基于DOM模型的、非验证的轻量级C++解释器, 用来读写配置文件是最好的,可从http://www.grinninglizard.com/tinyxml/index.html这里下载。构建TinyXML 时可以选择是否支持STL, 一般建议选择支持STL。TinyXml的源码包里提供了VC6的工程文件,直接用它就可以生成静态库。然后把tinyxml.h, tinyst...  阅读全文
posted @ 2007-04-04 17:14 true 阅读(1184) | 评论 (2)编辑 收藏

转贴-有关TinyXML使用的简单总结
floodking 发表于 2006-11-6 18:01:00

 这次使用了TinyXML后,觉得这个东西真是不错,于是将使用方法坐下总结来和大家分享。
    该解析库在开源网站(http://sourceforge.net )上有下载,在本Blog也提供下载(下载TinyXML
    TinyXML是一个开源的解析XML的解析库,能够用于C++,能够在WindowsLinux中编译。这个解析库的模型通过解析XML文件,然后在内存中生成DOM模型,从而让我们很方便的遍历这课XML树。
    注:DOM模型即文档对象模型,是将整个文档分成多个元素(如书、章、节、段等),并利用树型结构表示这些元素之间的顺序关系以及嵌套包含关系(理解html语言的读者会很容易理解这种树状模型)。               
    如下是一个XML片段:
    <Persons>
        <Person ID="1">
            <name>周星星</name>
            <age>20</age>
        </Person>
        <Person ID="2">
            <name>白晶晶</name>
            <age>18</age>
        </Person>
    </Persons>
    在TinyXML中,根据XML的各种元素来定义了一些类:
        TiXmlBase:整个TinyXML模型的基类。
                TiXmlAttribute:对应于XML中的元素的属性。
                TiXmlNode:对应于DOM结构中的节点。
                        TiXmlComment:对应于XML中的注释。
                        TiXmlDeclaration:对应于XML中的申明部分,即<?versiong="1.0" ?>。
                        TiXmlDocument:对应于XML的整个文档。
                        TiXmlElement:对应于XML的元素。
                        TiXmlText:对应于XML的文字部分。
                        TiXmlUnknown:对应于XML的未知部分。 
        TiXmlHandler:定义了针对XML的一些操作。
    那我们如何使用这些类以及他们的方法来操纵我们的XML呢?请看下面。
    一、读取XML(假设我们的Xml文档中的内容与上面的Xml内容一样)
    //创建一个XML的文档对象
    TiXmlDocument *myDocument = new TiXmlDocument("填上你的Xml文件名");
    myDocument->LoadFile();
    //获得根元素,即Persons。
    TiXmlElement *RootElement = myDocument.RootElement();
    //输出根元素名称,即输出Persons。
    cout << RootElement->Value() << endl;
    //获得第一个Person节点。
    TiXmlElement *FirstPerson = RootElement->FirstChildElement();
    //获得第一个Person的name节点和age节点和ID属性。
    TiXmlElement *NameElement = FirstPerson->FirstChildElement();
    TiXmlElement *AgeElement = NameElement->NextSiblingElement();
    TiXmlAttribute *IDAttribute = FirstPerson->FirstAttribute();
    //输出第一个Person的name内容,即周星星;age内容,即20;ID属性,即1。
    cout << NameElement->FirstChild()->Value << endl;
    cout << AgeElement->FirstChild()->Value << endl;
    cout << IDAttribute->Value() << endl;

    
    看,读取XML是不是很简单阿,和Java的XML解析库非常的相似,就是名字改了一下而已。
    二、生成XML内容
    //创建一个XML的文档对象。
    TiXmlDocument *myDocument = new TiXmlDocument();
    //创建一个根元素并连接。
    TiXmlElement *RootElement = new TiXmlElement("Persons");
    myDocument->LinkEndChild(RootElement);
    //创建一个Person元素并连接。
    TiXmlElement *PersonElement = new TiXmlElement("Person");
    RootElement->LinkEndChild(PersonElement);
    //设置Person元素的属性。
    PersonElement->SetAttribute("ID", "1");
    //创建name元素、age元素并连接。
    TiXmlElement *NameElement = new TiXmlElement("name");
    TiXmlElement *AgeElement = new TiXmlElement("age");
    PersonElement->LinkEndChild(NameElement);
    PersonElement->LinkEndChild(AgeElement);
    //设置name元素和age元素的内容并连接。
    TiXmlText *NameContent = new TiXmlText("周星星");
    TiXmlText *AgeContent = new TiXmlText("20");
    NameElement->LinkEndChild(NameContent);
    AgeElement->LinkEndChild(AgeContent);
    //保存到文件
    myDocument->SaveFile("要保存的xml文件名");
    这样,便创建了一个如下的xml文件:
    <Persons>
        <Person ID="1">
            <name>周星星</name>
            <age>20</age>
        </Person>
    </Persons>
    
    是不是很简单啊?在这里我只是简单的对TinyXml的使用作了介绍,欢迎各位提问留言,我会尽力解答各位的问题。[By Lqbest]
posted @ 2007-04-03 09:28 true 阅读(899) | 评论 (0)编辑 收藏

这篇文章摘自http://blog.csdn.net/kunp/archive/2004/06/30/30541.aspx
帮我解决了从 string::c_str 到 char *类型的转换问题。(注意,string::c_str返回的
是const char* 类型。

使用stringstream对象实现数据类型之间的转换

   很多人都使用传统的C 库来进行数据类型之间的转换,这将会导致很多问题,因为这样的转换方法存在很多危险的陷阱。比如itoa()这个函数在标准库中是不存在的。标准的库提供了一种更好的转换选择,因为这一方法更加安全,自动,直接。

   让我们来看一个具体的范例。假设你想把一个int转换为string。为了达到这一目的,你必须遵循如下的步骤:

1. 建立一个stringstream对象,

2.使用操作符<<插入int数据,

3.使用操作符>>抽取前面插入到的数据到一个string对象中。

以下代码行演示了这些步骤:


																//程序名:teststream.cpp //功能:将int类型数据通过stringstream对象转成string 
#i nclude <iostream>
#i nclude <stdlib.h>    // system()
#i nclude <string>
#i nclude <sstream>
using namespace std;
																

int main(int argc, char *argv[])
{
    std::stringstream stream;
    std::string result;
    int num = 1000;
   
    stream << num;                          //将int类型数据插入stream对象中
    stream >> result;                       //取出之前插入的数据
    cout << "num:\t" << num << endl;
    cout << "result:\t" << result << endl;    //打印 "1000"               
   
    system("PAUSE"); 
    return 0;
}


   请注意我们没有使用一个简洁的cast操作或一个模式标志来实现stringstream转换。操作符<<和>>会自动地删除原始数据的类型和目标数据,并自动而安全地执行需要的转换。

库不会只限于一些高水平的操作,比如std::string。你可以很方便地实现一个char *变量之间的转换:

												//程序名:teststream2.cpp //功能:将int类型数据通过stringstream对象转成char[] #i nclude <iostream> 
#i nclude <stdlib.h>    // system()
#i nclude <string>
#i nclude <sstream>
using namespace std;
												

int main(int argc, char *argv[])
{
    std::stringstream stream;
    char result[12] = {'\0'};
    stream << 1234;                     //insert int to stream
    stream >> result;                   //extract previously inserted value
    cout << result << endl;             // print "1234"            
   
    system("PAUSE"); 
    return 0;
}

   如果你想通过使用同一stringstream对象实现多种类型的转换,请注意在每一次转换之后都必须调用clear()成员函数,例如:

												//程序名:teststream3.cpp //功能:使用同一stringstream对象实现多种类型的转换 #i nclude <iostream> 
#i nclude <stdlib.h>    // system()
#i nclude <string>
#i nclude <sstream>
using namespace std;
												

int main(int argc, char *argv[])
{
    std::stringstream stream;
    int n, m;
    stream<< "456";       //insert string
    stream >> n;          //extract to int
    stream.clear();       //reset stream before another conversion
    stream<< true;        //insert bool value
    stream >> m;          //extract to int

    cout << "n:\t" << n << endl;    //print 456
    cout << "m:\t" << m << endl;    //print 1            
   
    system("PAUSE"); 
    return 0;
}

   事实上,stream对象可以接收多种类型输入的特点给我们带来一个好处,可以将int,char*等不同类型的输入同时导入到一个stream对象,再通过该stream对象导出一个新值。

												//程序名:teststream4.cpp //功能:将int类型数据和char*数据通过stringstream对象转成char[] 
#i nclude <iostream>
#i nclude <stdlib.h>    // system()
#i nclude <string>
#i nclude <sstream>
using namespace std;
												

int main(int argc, char *argv[])
{
    std::stringstream stream;
    char ip[16];
    stream << 218; //insert int
    stream << "."; //insert string
    stream << 192; //insert int
    stream << "."; //insert string
    stream << 160; //insert int
    stream << "."; //insert string
    stream << 228; //insert int
   
    stream >> ip;

    cout << "ip:\t" << ip << endl;    //print " 218.192.160.228"    
    system("PAUSE"); 
    return 0;

posted @ 2007-03-29 16:25 true 阅读(1418) | 评论 (0)编辑 收藏

仅列出标题
共15页: First 7 8 9 10 11 12 13 14 15