??xml version="1.0" encoding="utf-8" standalone="yes"?>亚洲精品国产福利一二区,亚洲欧洲日韩国产一区二区三区,国产成+人+综合+亚洲专http://m.tkk7.com/shiliqiang/articles/290421.html矛_@矛_@Sun, 09 Aug 2009 03:09:00 GMThttp://m.tkk7.com/shiliqiang/articles/290421.htmlhttp://m.tkk7.com/shiliqiang/comments/290421.htmlhttp://m.tkk7.com/shiliqiang/articles/290421.html#Feedback0http://m.tkk7.com/shiliqiang/comments/commentRss/290421.htmlhttp://m.tkk7.com/shiliqiang/services/trackbacks/290421.html一Q基本概?br /> ? 拯Qzero-copyQ基本思想是:数据报从|络讑֤到用L序空间传递的q程中,减少数据拯ơ数Q减系l调用,实现CPU的零参与Q彻底消? CPU在这斚w的负载。实现零拯用到的最主要技术是DMA数据传输技术和内存区域映射技术。如?所C,传统的网l数据报处理Q需要经q网l设备到操作 pȝ内存I间Q系l内存空间到用户应用E序I间q两ơ拷贝,同时q需要经历用户向pȝ发出的系l调用。而零拯技术则首先利用DMA技术将|络数据报直? 传递到pȝ内核预先分配的地址I间中,避免CPU的参与;同时Q将pȝ内核中存储数据报的内存区域映到程序的应用E序I间Q还有一U方式是在用L 间徏立一~存Qƈ其映射到内核空_cM于linuxpȝ下的kiobuf技术)Q检程序直接对q块内存q行讉KQ从而减了pȝ内核向用L间的? 存拷贝,同时减少了系l调用的开销Q实C真正?#8220;零拷?#8221;?br />

? 传统数据处理与零拯技术之比较
二.实现
在redhat7.3 上通过修改其内核源码中附带?139too.c完成零拷贝的试验Q主要想法是Q在8139too|卡驱动模块启动时申请一内核~存Qƈ建立一数据l构? 其进行管理,然后试验性的向该~存写入多个字符串数据,最后通过proc文gpȝ该~存的地址传给用户q程Q用戯E通过读proc文gpȝ取得~存? 址q对该缓存进行地址映射Q从而可以从其中d数据。哈哈,ZhQ本文只是对零拷贝思想中的地址映射部分q行试验Q而没有实现DMA数据传输Q太ȝ 了,q得了解gQ,本试验ƈ不是一个IDS产品中抓包模块的一部分Q要想真正在IDS中实现零拯Q除了DMA外,q有一些问题需考虑Q详见本文第三节 的分析。以下ؓ实现零拷贝的主要步骤Q详l代码见附录?br />
步骤一Q修改网卡驱动程?/font>
aQ在|卡驱动E序中申请一块缓?/font>Q由于在linux2.4.X内核中支持的最大可分配q箋~存大小?MQ所以如果需要存储更大量的网l数据报文,则需要分配多块非q箋的缓存,q用链表、数l或hash表来对这些缓存进行管理?br />
#define PAGES_ORDER 9
unsigned long su1_2
su1_2 = __get_free_pages(GFP_KERNEL,PAGES_ORDER);

b. 向缓存中写入数据Q真正IDS产品中的零拷贝实现应该是使用DMA数据传输把网卡硬 件接收到的包直接写入该缓存。作验,我只是向该缓存中写入几个L 的字W串Q如果不考虑DMA而又惛_~存中写入真正的|络数据包,可以?139too.c的rtl8139_rx_interrupt()中调? netif_rx()后插入以下代码:

//put_pkt2mem_n++; //包个?br /> //put_mem(skb->data,pkt_size);
其中put_pkt2mem_n变量和put_mem函数见附录?br />
c. 把该~存的物理地址传到用户I间Q由于在内核中申L~存地址拟地址Q而在用户 I间需要得到的是该~存的物理地址Q所以首先要q行虚拟地址到物理地址 的{换,在linuxpȝ中可以用内核虚拟地址?G来获得对应的物理地址。把~存的地址传到用户I间需要在内核与用L间进行少量数据传输,q可以 用字W驱动、proc文gpȝ{方式实玎ͼ在这里采用了proc文gpȝ方式?br />
int read_procaddr(char *buf,char **start,off_t offset,int count,int *eof,void *data)
{
     sprintf(buf,"%u\n",__pa(su1_2));
     *eof = 1;
     return 9;
}
create_proc_read_entry("nf_addr",0,NULL,read_procaddr,NULL);

步骤二:在用L序中实现对共享缓存的讉K
a.d~存地址Q通过直接dproc文g的方式便可获得?br />
char addr[9];
int fd_procaddr;
unsigned long ADDR;
fd_procaddr = open("/proc/nf_addr",O_RDONLY);
read(fd_procaddr,addr,9);
ADDR = atol(addr);

b.把缓存映到用户q程I间?/font>Q在用户q程中打开/dev/mem讑֤(相当于物理内存)Q用mmap把网卡驱动程序申L~存映射到自qq程I间Q然后就可以从中d所需要的|络数据包了?br />
char *su1_2;
int fd;
fd=open("/dev/mem",O_RDWR);    
su1_2 = mmap(0,PAGES*4*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, ADDR);

三.分析
     零拷贝中存在的最关键问题是同步问题,一Ҏ处于内核I间的网卡驱动向~存中写入网l数据包Q一Ҏ用户q程直接对缓存中的数据包q行分析Q注意,不是? 贝后再分析)Q由于两者处于不同的I间Q这使得同步问题变得更加复杂。缓存被分成多个块Q每一块存储一个网l数据包q用一数据l构表示Q本试验在包数据 l构中用标志位来标识什么时候可以进行读或写Q?font color="#0000ff">当网卡驱动向包数据结构中填入真实的包数据后便标识该包为可读,当用戯E对包数据结构中的数据分析完?便标识该包ؓ可写Q这基本解决了同步问?/font>。然而,׃IDS的分析进E需要直接对~存中的数据q行入R分析Q而不是将数据拯到用L间后再进行分析,q?使得L作要慢于写操作,有可能造成|卡驱动无缓存空间可以写Q从而造成一定的丢包现象Q解册一问题的关键在?font color="#0000ff">甌多大的缓?/font>Q太的~存Ҏ造成?包,太大的缓存则理ȝq且对系l性能会有比较大的影响?br />
四.附录
a.     8139too.c中加入的代码

/*add_by_liangjian for zero_copy*/
#include <linux/wrapper.h>
#include <asm/page.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#define PAGES_ORDER 9
#define PAGES 512
#define MEM_WIDTH     1500
/*added*/

/*add_by_liangjian for zero_copy*/
struct MEM_DATA
{
     //int key;
     unsigned short width;/*~冲区宽?/
     unsigned short length;/*~冲区长?/
     //unsigned short wtimes;/*写进E记?预留Qؓ以后可以多个q程?/
     //unsigned short rtimes;/*读进E记?预留Qؓ以后可以多个q程?/
     unsigned short wi;/*写指?/
     unsigned short ri;/*L?/
} * mem_data;
struct MEM_PACKET
{
     unsigned int len;
     unsigned char packetp[MEM_WIDTH - 4];/*sizeof(unsigned int) == 4*/
};
unsigned long su1_2;/*~存地址*/
/*added*/

/*add_by_liangjian for zero_copy*/
//删除~存
void del_mem()
{
     int pages = 0;
     char *addr;
     addr = (char *)su1_2;
     while (pages <=PAGES -1)
     {
         mem_map_unreserve(virt_to_page(addr));
         addr = addr + PAGE_SIZE;
         pages++;
     }
     free_pages(su1_2,PAGES_ORDER);    
}    
void init_mem()
/********************************************************
*                   初始化缓?br /> *       输入:   aMode:     ~冲写模?   r,w         *
*       q回:   00:     p|                         *
*               >0:     ~冲区地址                   *
********************************************************/
{
     int i;
     int pages = 0;
     char *addr;
     char *buf;
     struct MEM_PACKET * curr_pack;
    
     su1_2 = __get_free_pages(GFP_KERNEL,PAGES_ORDER);
     printk("[%x]\n",su1_2);
     addr = (char *)su1_2;
     while (pages <= PAGES -1)
     {
         mem_map_reserve(virt_to_page(addr));//需使缓存的面帔R内存
         addr = addr + PAGE_SIZE;
         pages++;
     }
     mem_data = (struct MEM_DATA *)su1_2;
     mem_data[0].ri = 1;
           mem_data[0].wi = 1;
           mem_data[0].length = PAGES*4*1024 / MEM_WIDTH;
           mem_data[0].width = MEM_WIDTH;
     /* initial su1_2 */
     for(i=1;i<=mem_data[0].length;i++)
     {
         buf = (void *)((char *)su1_2 + MEM_WIDTH * i);
         curr_pack = (struct MEM_PACKET *)buf;
         curr_pack->len = 0;
     }    
}
int put_mem(char *aBuf,unsigned int pack_size)
/****************************************************************
*                 写缓冲区子程?nbsp;                                *
*       输入参数     :   aMem:   ~冲区地址                       *
*                       aBuf:   写数据地址                       *
*       输出参数     :   <=00 :   错误                             *
*                       XXXX :   数据序?nbsp;                      *
*****************************************************************/
{
     register int s,i,width,length,mem_i;
     char *buf;
     struct MEM_PACKET * curr_pack;

     s = 0;
     mem_data = (struct MEM_DATA *)su1_2;
     width   = mem_data[0].width;
     length = mem_data[0].length;
     mem_i   = mem_data[0].wi;
     buf = (void *)((char *)su1_2 + width * mem_i);

     for (i=1;i<length;i++){
         curr_pack = (struct MEM_PACKET *)buf;
             if   (curr_pack->len == 0){
                     memcpy(curr_pack->packetp,aBuf,pack_size);
                     curr_pack->len = pack_size;;
                 s = mem_i;
             mem_i++;
                     if   (mem_i >= length)
                         mem_i = 1;
                 mem_data[0].wi = mem_i;
                 break;
             }
             mem_i++;
             if   (mem_i >= length){
                     mem_i = 1;
                     buf = (void *)((char *)su1_2 + width);
             }
             else buf = (char *)su1_2 + width*mem_i;
         }

     if(i >= length)
             s = 0;
     return s;
}
// proc文gd?br /> int read_procaddr(char *buf,char **start,off_t offset,int count,int *eof,void *data)
{
     sprintf(buf,"%u\n",__pa(su1_2));
     *eof = 1;
     return 9;
}
/*added*/

?139too.c的rtl8139_init_module()函数中加入以下代码:
/*add_by_liangjian for zero_copy*/
     put_pkt2mem_n = 0;
     init_mem();
     put_mem("data1dfadfaserty",16);
     put_mem("data2zcvbnm",11);
     put_mem("data39876543210poiuyt",21);
     create_proc_read_entry("nf_addr",0,NULL,read_procaddr,NULL);
/*added */    

?139too.c的rtl8139_cleanup_module()函数中加入以下代码:
/*add_by_liangjian for zero_copy*/
     del_mem();
     remove_proc_entry("nf_addr",NULL);
/*added*/    

bQ用L间读取缓存代?/font>

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#define PAGES 512
#define MEM_WIDTH 1500
struct MEM_DATA
{
     //int key;
     unsigned short width;/*~冲区宽?/
     unsigned short length;/*~冲区长?/
     //unsigned short wtimes;/*写进E记?预留Qؓ以后可以多个q程?/
     //unsigned short rtimes;/*读进E记?预留Qؓ以后可以多个q程?/
     unsigned short wi;/*写指?/
     unsigned short ri;/*L?/
} * mem_data;

struct MEM_PACKET
{
     unsigned int len;
     unsigned char packetp[MEM_WIDTH - 4];/*sizeof(unsigned int) == 4*/
};

int get_mem(char *aMem,char *aBuf,unsigned int *size)
/****************************************************************
*                 ȝ冲区子程?nbsp;                                *
*       输入参数     :   aMem:   ~冲区地址                       *
*                       aBuf:   q回数据地址, 其数据区长度应大?
*                               ~冲区宽?nbsp;                      *
*       输出参数     :   <=00 :   错误                             *
*                       XXXX :   数据序?nbsp;                      *
*****************************************************************/
{
     register int i,s,width,length,mem_i;
     char     *buf;
     struct MEM_PACKET * curr_pack;

     s = 0;
     mem_data = (void *)aMem;
     width   = mem_data[0].width;
     length = mem_data[0].length;
     mem_i   = mem_data[0].ri;
     buf = (void *)(aMem + width * mem_i);

     curr_pack = (struct MEM_PACKET *)buf;
     if   (curr_pack->len != 0){/*W一个字节ؓ0说明该部分ؓI?/
             memcpy(aBuf,curr_pack->packetp,curr_pack->len);
             *size = curr_pack->len;
             curr_pack->len = 0;
             s = mem_data[0].ri;
             mem_data[0].ri++;
             if(mem_data[0].ri >= length)
                     mem_data[0].ri = 1;
             goto ret;
         }
    
     for (i=1;i<length;i++){
             mem_i++;/*l箋向后找,最p糕的情冉|把整个缓冲区都找一?/
             if   (mem_i >= length)
                 mem_i = 1;
             buf = (void *)(aMem + width*mem_i);
             curr_pack = (struct MEM_PACKET *)buf;
             if   (curr_pack->len == 0)
                     continue;
             memcpy(aBuf,curr_pack->packetp,curr_pack->len);
             *size = curr_pack->len;
             curr_pack->len = 0;
             s = mem_data[0].ri = mem_i;
             mem_data[0].ri++;
             if(mem_data[0].ri >= length)
             mem_data[0].ri = 1;
             break;
         }

     ret:
     return s;
}

int main()
{
     char *su1_2;
     char receive[1500];
     int i,j;
     int fd;
     int fd_procaddr;
     unsigned int size;
     char addr[9];
     unsigned long ADDR;
    
     j = 0;
     /*open device 'mem' as a media to access the RAM*/
     fd=open("/dev/mem",O_RDWR);    
     fd_procaddr = open("/proc/nf_addr",O_RDONLY);
     read(fd_procaddr,addr,9);
     ADDR = atol(addr);
     close(fd_procaddr);
     printf("%u[%8lx]\n",ADDR,ADDR);
     /*Map the address in kernel to user space, use mmap function*/
     su1_2 = mmap(0,PAGES*4*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, ADDR);
     perror("mmap");
     while(1)
     {
         bzero(receive,1500);
         i = get_mem(su1_2,receive,&size);
         if (i != 0)
         {
             j++;
             printf("%d:%s[size = %d]\n",j,receive,size);
         }    
         else
         {
             printf("there have no data\n");
             munmap(su1_2,PAGES*4*1024);
             close(fd);
             break;
         }
     }
     while(1);
}

五.参考文?br /> 1QCHRISTIAN KURMANN, FELIX RAUCH ,THOMAS M. STRICKER.
Speculative Defragmentation - Leading Gigabit Ethernet to True Zero-Copy Communication
2QALESSANDRO RUBINI,JONATHAN CORBET.《LINUX DEVICE DRIVERS 2?O’Reilly & Associates 2002.
3Q胡希明,毛d?《LINUX 内核源代码情景分析?江大学出版C?2001


? 于作者:梁健Q华北计技术研I所在读士研究生,研究方向Q信息安全。论文开题ؓ《基于系l调用分析的L异常入R与防M》。对IDS有两q多的研 I经验,熟悉linux内核Q熟悉linux c/c++~程、win32 API~程Q对|络和操作系l安全感兴趣?br /> @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
零拷贝技术分Z步:
1、硬件到内核Q实现的前提是网卡必L持DMAQ对于不支持DMA的网卡无法实现零拯?
2、内核到用户层,系l内怸存储数据报的内存区域映射到检程序的应用E序I间或者在用户I间建立一~存Qƈ其映射到内核空间?
很多相关公司都采用了q种技术Firewall/IDS{,q两U技术已l很成熟?br />
摘自Qhttp://hi.baidu.com/msingle/blog/item/0ec4eb239db94e40ad34de18.html


矛_@ 2009-08-09 11:09 发表评论
]]>
盘 ?/title><link>http://m.tkk7.com/shiliqiang/articles/290389.html</link><dc:creator>矛_@</dc:creator><author>矛_@</author><pubDate>Sat, 08 Aug 2009 14:31:00 GMT</pubDate><guid>http://m.tkk7.com/shiliqiang/articles/290389.html</guid><wfw:comment>http://m.tkk7.com/shiliqiang/comments/290389.html</wfw:comment><comments>http://m.tkk7.com/shiliqiang/articles/290389.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://m.tkk7.com/shiliqiang/comments/commentRss/290389.html</wfw:commentRss><trackback:ping>http://m.tkk7.com/shiliqiang/services/trackbacks/290389.html</trackback:ping><description><![CDATA[<div id="e8y4oes" class="tit"> </div> <div id="goms2cy" class="cnt"> <p>? 件系l是操作pȝ与驱动器之间的接口,当操作系l请求从盘里读取一个文件时Q会h相应的文件系l(FAT 16/32/NTFSQ打开文g。扇区是盘最的物理存储单元Q但׃操作pȝ无法Ҏ目众多的扇区q行dQ所以操作系l就相ȝ扇区l合在一P 形成一个簇Q然后再对簇q行理。每个簇可以包括2???6?2?4个扇区。显Ӟ是操作pȝ所使用的逻辑概念Q而非盘的物理特性?<br /> <br /> Z更好地管理磁盘空间和更高效地从硬盘读取数据,操作pȝ规定一个簇中只能放|一个文件的内容Q因此文件所占用的空_只能是簇的整数倍;而如果文件实 际大小于一,它也要占一的I间。所以,一般情况下文g所占空间要略大于文件的实际大小Q只有在数情况下,x件的实际大小恰好是簇的整数倍时Q文 件的实际大小才会与所占空间完全一致?/p> <p>? 件系l是操作pȝ与驱动器之间的接口,当操作系l请求从盘里读取一个文件时Q会h相应的文件系l(FAT 16/32/NTFSQ打开文g。扇区是盘最的物理存储单元Q但׃操作pȝ无法Ҏ目众多的扇区q行dQ所以操作系l就相ȝ扇区l合在一P 形成一个簇Q然后再对簇q行理。每个簇可以包括2???6?2?4个扇区。显Ӟ是操作pȝ所使用的逻辑概念Q而非盘的物理特性?<br /> <br /> Z更好地管理磁盘空间和更高效地从硬盘读取数据,操作pȝ规定一个簇中只能放|一个文件的内容Q因此文件所占用的空_只能是簇的整数倍;而如果文件实 际大小于一,它也要占一的I间。所以,一般情况下文g所占空间要略大于文件的实际大小Q只有在数情况下,x件的实际大小恰好是簇的整数倍时Q文 件的实际大小才会与所占空间完全一致?/p> <div id="0ysuwi4" class="f14">是指可分配的用来保存文件的最磁盘空_计算Z所有的信息都保存在中。簇小Q保存信息的效率p高。在FAT16文gpȝ中,每个分区最多有65525个簇Q簇大小默认gؓ32KBQ在FAT32文gpȝ中用的比FAT16,默认?KB?<br /> 那么在NTFS文gpȝ中磁盘簇的大设为多才合适呢?下面看看大家的讨论: <br /> <br /> 一、在NTFS文gpȝ中如何设|簇大小 <br /> <br /> 默认的情况下Q在格式化的时候如果没有指定簇的大,那么pȝ会根据分区的大小选择默认的簇倹{其实在NTFS文gpȝ中格式化的时候,可以? “Format”命o后面d“/a:UnitSize ”参数来指定簇的大,UnitSize表示大的|NTFS支持512/1024/2048/4096/8192/16K/32K/64K。比? “format d:/fs:NTFS /a:2048”Q表C将D盘用NTFS文gpȝ格式化,的gؓ2048B?<br /> <br /> 二、用默认的讄 <br /> <br /> 对于初学者来_其实没有必要L工设|簇的大,因ؓ一般情况下使用默认的设|就可以了。比如在用NTFS文gpȝ格式化分区的时候,pȝ会根据分区的大小自动选择默认的簇大小Q比?KB?<br /> <br /> 三、簇的大因盘分区大小而异 <br /> <br /> 在NTFS文gpȝ中,当分区的大小?GB以下Ӟ的大小应该比相应的FAT32小Q即于4KBQ当分区的大在2GB以上Ӟ2GB~2TBQ,的大小应该都ؓ4KB?<br /> <br /> 四、用压~功能对大的要求 <br /> <br /> 在Windows 2000/XPpȝ中,Z使用压羃功能来节省磁盘空_必须遵@两个条gQ?<br /> <br /> 1Q磁盘分区必LNTFS文gpȝQ?<br /> 2Q分Z的大小不得过4KBQ默认簇的大,?096字节Q?<br /> <br /> 五、簇的大的影响 <br /> <br /> 在NTFS文gpȝ中,的大小会媄响到盘文g的排列,讄适当的簇大小可以减少盘I间丢失和分Z片的数量。如果簇讄q大Q会影响到磁盘存储效率;反之如果讄q小Q虽然会提高利用效率Q但是会产生大量盘片?/div> <div id="ucu4q4a" class="f14"> <div id="2myseoq" class="f14">? 盘是计算Z极ؓ重要的存储设备,计算机工作所用到的全部文件系l和数据资料的绝大多数都存储在硬盘中。硬盘是产生计算Y故障最主要的地方,常见的硬? 软故障有Q硬盘重要参数及文g丢失Q电脑不能v动;片q多Q电脑运行速度变慢Q硬盘分区后丢失定w{。对付硬盘Y故障Q只要我们肯动脑q利用一些硬盘维 护工P发挥一不怕苦、二不怕(盘Q死的革命精,外加胆大心细Q当然还要掌握硬盘基本常识,q样可以轻松搞定(说的Ҏ、做h可不?:( Q。因此,我收集了大量的资料整理汇~了“盘软故障完全修复手?#8221;Q希望能在与大家一起学习的q程中掌握硬盘常见故障的排除ҎQ做?#8220;自已动手、丰? 食”Q凡事不求h的目的?<br /> 大家知道Q一个硬盘要能存放文Ӟ必须l过盘分区Q格式化{操作步骤,因ؓl过q些步骤之后Q在盘中就建立起了d区,引导分区Q确定了FAT16? FAT32文g表。主分区的作用是保存盘中各逻辑分区在盘片上起始位置和终止位|及分区的容量大。引导分区的作用是在固定的位|存放有操作pȝ文gQ? 在电脑送电或复位时Q由BIOSE序处于固定位|的pȝ文g装入内存Q再电脑控制权交给pȝ文g完成引DE。扩展分ZZ个主分区占用了主? 的一个表V在扩展分区起始位置所指示的扇区(卌分区的第一个扇区)中,包含有第一个逻辑分区表,同样?BEH字节开始,每个分区表项占用16? 字节。逻辑分区表一般包含两个分,一个指向某逻辑分区Q另一个则指向下一个扩展分区。下一个扩展分区的首扇区又包含了一个逻辑分区表, <br /> q样以此cLQ扩展分Z可以包含多个逻辑分区。下面我们就来学习一下硬盘数据的基本l构?<br /> ?盘的数据结??<br /> ?MBRQMain Boot Record dD录区Q?<br /> MBR位于整个盘?道0柱面1扇区Q包括硬盘引导程序和分区表。在d512字节的硬盘主引导扇区中,MBR只占用了其中?46个字节,其最后两 个字?#8220;55 AA”是分区的l束标志。另外的64个字节交l了DPTQDisk Partition Table 盘分区表)Q从1BEH字节开始,共占?4个字节,包含四个分区表项。每个分的长度?6个字节,它包含一个分区的引导标志、系l标志、v始和 l尾的柱面号、扇区号、磁头号以及本分区前面的扇区数和本分区所占用的扇区数。其?#8220;引导标志”表明此分区是否可引导Q即是否zd分区。当引导标志? “80”Ӟ此分Zؓzd分区Q?#8220;pȝ标志”军_了该分区的类型,?#8220;06”为FAT16分区Q?#8220;0B”为FAT32分区Q?#8220;07”为NTFS? 区,“63”为UNIX分区Q等Qv始和l尾的柱面号、扇区号、磁头号指明了该分区的v始和l止位置?<br /> 我们假设一个硬盘分?BEH字节开始的16个字节ؓ 80 01 01 00 06 0D 68 6D 28 00 00 00 78 20 03 00 <br /> 盘分区表项?6个字节分配如下: <br /> W?字节Q是一个分区的ȀzL志,表示pȝ可引对{如?则表C非zd分区?<br /> W?字节Q该分区起始头QHEADQ号 <br /> W?字节Q该分区起始扇区QSectorQ号 <br /> W?字节Q该分区起始的柱面(CylinderQ号 <br /> W?字节Q该分区pȝcd标志 <br /> W??字节Q该分区l止头QHEADQ号、分区结束的扇区受分区结束的柱面?<br /> W?-12字节Q该分区首扇区的相对扇区?<br /> W?3-16字节Q该分区占用的扇区L <br /> 以上参数我们可以用NU 8.0中DISKEDIT工具软g可轻松获取,其功能非常强大,但应用不当会有很大错误,请各位注意用方法。操作步骤如下: <br /> 以一台硬盘ؓ270 MBQ分为C盘(100 MBQ和D盘(170 MBQ的机子Q老掉牙了 ^_^Qؓ例,在纯DOS下启动DISKEDIT → 在对象菜单(ObjectQ上选中驱动器(DriveQ和物理盘选项后确?→ 在对象菜单(ObjectQ上选中分区表(Partition TableQ?→ 在显C单(ViewQ中选择十六q制QHexQ?<br /> 以下数据Z分区信息Q?<br /> 000001B0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 80 01 <br /> 000001C0: 01 00 06 0D 68 6D 28 00 - 00 00 78 20 03 00 00 00 <br /> 000001D0: 41 6E 05 0D E8 AE A0 20 - 03 00 30 EE 04 00 00 00 <br /> 000001E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 <br /> 000001F0: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 55 AA <br /> ?DBRQDos Boot Record 操作pȝ引导记录区) <br /> 它通常位于盘?道1柱面1扇区Q是操作pȝ可直接访问的W一个扇区,它包括一个引导程序和一个被UCؓBPBQBIOS Parameter BlockQ的本分区参数记录表。引导程序的主要d是当MBR系l控制权交给它时Q判断本分区跟目录前两个文g是不是操作系l的引导文gQ以DOS? 例,xIo.sys和Msodos.sysQ。如果确定存在,把它们d内存Qƈ把控制权交给该文件。BPB参数块记录着本分区的起始扇区、结束扇 区、文件存储格式、硬盘介质描q符、根目录大小、FAT个数、分配单元的大小{重要参数。DBR是由高格式化程序(即Format{程序)所产生的? <br /> ?FATQFile Allocation Table 文g分配表) <br /> FAT是DOS、Windows 9Xpȝ的文件寻址格式Q位于DBR之后?<br /> 在解释文件分配表的概늚时候,我们有必要谈谈簇QClusterQ的概念。文件占用磁盘空_基本单位不是字节而是。一般情况下QY盘每是1个扇区,盘每簇的扇区数与硬盘的d量大有养I可能???6?2?4…… <br /> 同一个文件的数据q不一定完整地存放在磁盘的一个连l的区域内,而往往会分成若q段Q像一条链子一样存放。这U存储方式称为文件的铑ּ存储。由于硬盘上? 存着D与D之间的q接信息Q即FATQ,操作pȝ在读取文件时QL能够准确地找到各D늚位置q正读出?<br /> Z实现文g的链式存储,盘上必d地记录哪些已l被文g占用Q还必须为每个已l占用的指明存储后l内容的下一个簇的簇受对一个文件的最后一 ,则要指明本簇无后l簇。这些都是由FAT表来保存的,表中有很多表,每项记录一个簇的信息。由于FAT对于文g理的重要性,所以ؓ了安全v 见,FAT有一个备份,卛_原FAT的后面再Z个同LFAT。初形成的FAT中所有项都标明ؓ“未占?#8221;Q但如果盘有局部损坏,那么格式化程序会 出损坏的簇Q在相应的项中标?#8220;坏簇”Q以后存文g时就不会再用这个簇了。FAT的项C盘上的ȝ数相当,每一占用的字节C要与ȝ数相? 应,因ؓ其中需要存攄受FAT的格式有多种Q最为常见的是FAT16和FAT32?<br /> ?DIR QDirectory 根目录区Q?<br /> DIR位于W二个FAT表之后,记录着根目录下每个文gQ目录)的v始单元,文g的属性等。定位文件位|时Q操作系l根据DIR中的起始单元Q结合FAT表就可以知道文g在硬盘中的具体位|和大小了?<br /> ?DATAQ数据区Q?<br /> 数据区是真正意义上的数据存储的地方,位于DIRZ后,占据盘的大部分I间。当数据复制到盘Ӟ数据存攑֜DATA区?/div> </div> </div> <img src ="http://m.tkk7.com/shiliqiang/aggbug/290389.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://m.tkk7.com/shiliqiang/" target="_blank">矛_@</a> 2009-08-08 22:31 <a href="http://m.tkk7.com/shiliqiang/articles/290389.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个正则表辑ּ工具c?/title><link>http://m.tkk7.com/shiliqiang/articles/290312.html</link><dc:creator>矛_@</dc:creator><author>矛_@</author><pubDate>Sat, 08 Aug 2009 01:01:00 GMT</pubDate><guid>http://m.tkk7.com/shiliqiang/articles/290312.html</guid><wfw:comment>http://m.tkk7.com/shiliqiang/comments/290312.html</wfw:comment><comments>http://m.tkk7.com/shiliqiang/articles/290312.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://m.tkk7.com/shiliqiang/comments/commentRss/290312.html</wfw:commentRss><trackback:ping>http://m.tkk7.com/shiliqiang/services/trackbacks/290312.html</trackback:ping><description><![CDATA[     摘要: 一个java正规表达式工L cM用到?jakarta-oro-2.0.jar 包,请大家自己在 apache|站下下? 在这是junit试单元cL׃提交了,在main()Ҏ中有几个测试,有兴自q? q个工具cȝ前主要有25U正规表辑ּ(有些不常用,但那时才仔细深入的研I了一下正规,写上瘾了Q就当时能想到的都写?: 匚w图象; 2 匚wemail?..  <a href='http://m.tkk7.com/shiliqiang/articles/290312.html'>阅读全文</a><img src ="http://m.tkk7.com/shiliqiang/aggbug/290312.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://m.tkk7.com/shiliqiang/" target="_blank">矛_@</a> 2009-08-08 09:01 <a href="http://m.tkk7.com/shiliqiang/articles/290312.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个合格的E序员应具备的。。?/title><link>http://m.tkk7.com/shiliqiang/articles/289318.html</link><dc:creator>矛_@</dc:creator><author>矛_@</author><pubDate>Fri, 31 Jul 2009 12:45:00 GMT</pubDate><guid>http://m.tkk7.com/shiliqiang/articles/289318.html</guid><wfw:comment>http://m.tkk7.com/shiliqiang/comments/289318.html</wfw:comment><comments>http://m.tkk7.com/shiliqiang/articles/289318.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://m.tkk7.com/shiliqiang/comments/commentRss/289318.html</wfw:commentRss><trackback:ping>http://m.tkk7.com/shiliqiang/services/trackbacks/289318.html</trackback:ping><description><![CDATA[每个E序员都应牢记的7U坏味道Q?1U原则,23U模? <br /> <br /> (一)7U设计坏味道 <br /> 1.僵化性: 很难对系l进行改动,因ؓ每个改动都会q许多对系l其他部分的其它改动? <br /> 2.脆弱性: 对系l的改动会导致系l中和改动的地方在概念上无关的许多地方出现问题? <br /> 3.牢固性: 很难解开pȝ的纠l,使之成ؓ一些可在其他系l中重用的组件? <br /> 4._滞性: 做正的事情比做错误的事情要困难? <br /> 5.复杂?不必要的)Q?设计中包含有不具M直接好处的基l构? <br /> 6.重复?不必要的)Q?设计中包含有重复的结构,而该重复的结构本可以使用单一的抽象进行统一? <br /> 7.晦ӆ性: 很难阅读、理解。没有很好地表现出意图? <br /> <br /> (?11U原?- Principle <br /> ----cd? <br /> 1.单一职责原则 - Single Responsibility Principle(SRP) <br /> ׃个类而言Q应该仅有一个引起它变化的原因? <br /> (职责即ؓ“变化的原?#8221;? <br /> 2.开?闭原则 - Open Close Principle(OCP) <br /> 软g实体Q类、模块、函数等Q应该是可以扩展的,但是不可修改? <br /> (对于扩展是开攄,对于更改是封闭的. <br /> 关键是抽?一个功能的通用部分和实现细节部分清晰的分离开? <br /> 开发h员应该仅仅对E序中呈现出频繁变化的那些部分作出抽? <br /> 拒绝不成熟的抽象和抽象本w一样重? ) <br /> 3.里氏替换原则 - Liskov Substitution Principle(LSP) <br /> 子类?subclass)必须能够替换掉它们的基类?superclass)? <br /> 4.依赖倒置原则(IoCP) ?依赖注入原则 - Dependence Inversion Principle(DIP) <br /> 抽象不应该依赖于l节。细节应该依赖于抽象? <br /> (Hollywood原则: "Don't call us, we'll call you". <br /> E序中所有的依赖关系都应该终止于抽象cd接口? <br /> 针对接口而非实现~程? <br /> M变量都不应该持有一个指向具体类的指针或引用? <br /> Mc都不应该从具体cL生? <br /> MҎ都不应该覆写他的M基类中的已经实现了的Ҏ? <br /> 5.接口隔离原则(ISP) <br /> 不应该强q客户依赖于它们不用的方法? <br /> 接口属于客户Q不属于它所在的cdơ结构? <br /> (多个面向特定用户的接口胜于一个通用接口? <br /> ----包内聚原? <br /> 6.重用发布{h原则(REP) <br /> 重用的粒度就是发布的_度? <br /> 7.共同闭原则(CCP) <br /> 包中的所有类对于同一cL质的变化应该是共同闭的? <br /> 一个变化若对一个包产生影响Q? <br /> 则将对该包中的所有类产生影响Q? <br /> 而对于其他的包不造成M影响? <br /> 8.共同重用原则(CRP) <br /> 一个包中的所有类应该是共同重用的? <br /> 如果重用了包中的一个类Q? <br /> 那么p重用包中的所有类? <br /> (怺之间没有紧密联系的类不应该在同一个包中? <br /> ----包耦合原则 <br /> 9.无环依赖原则(ADP) <br /> 在包的依赖关pd中不允许存在环? <br /> 10.E_依赖原则(SDP) <br /> 朝着E_的方向进行依赖? <br /> 应该把封装系l高层设计的软gQ比如抽象类Q放q稳定的包中Q? <br /> 不稳定的包中应该只包含那些很可能会改变的软gQ比如具体类Q? <br /> 11.E_抽象原则(SAP) <br /> 包的抽象E度应该和其E_E度一致? <br /> (一个稳定的包应该也是抽象的Q一个不E_的包应该是抽象的. ) <br /> ----其它扩展原则---- <br /> 12.BBP(Black Box Principle)黑盒原则 <br /> 多用cȝ聚合Q少用类的ѝ? <br /> 13.DAP(Default Abstraction Principle)~省抽象原则 <br /> 在接口和实现接口的类之间引入一个抽象类,q个cdC接口的大部分操作. <br /> 14.IDP(Interface Design Principle)接口设计原则 <br /> 规划一个接口而不是实C个接口? <br /> 15.DCSP(Don't Concrete Supperclass Principle)不要构造具体的类原则 <br /> 避免l护具体的超cR? <br /> 16.q米Ҏ? <br /> 一个类只依赖其触手可得的类? <br /> <br /> (?23U设计模?- Pattern. <br /> 创徏? <br /> Abstract FactoryQ抽象工厂模式) -> (单工厂模? <br /> Factory MethodQ工厂模式) <br /> BuilderQ生成器模式Q? <br /> SingletonQ单件模式) -> (多例模式) <br /> PrototypeQ原型模式) <br /> l构? <br /> AdapterQ适配器模式) <br /> BridgeQ桥接模式) <br /> CompositeQ组合模式) <br /> DecoratorQ装饰模式) <br /> FacadeQ外观模式,门面模式Q? <br /> FlyweightQn元模式) -> (不变模式) <br /> ProxyQ代理模式) <br /> 行ؓ? <br /> Chain of ResponsibilityQ职责链模式Q? <br /> CommandQ命令模式) <br /> InterpreterQ解释器模式Q? <br /> IteartorQP代器模式Q? <br /> MediatorQ中介者模式) <br /> MementoQ备忘录模式Q? <br /> ObserverQ观察者模式) <br /> StateQ状态模式) <br /> StrategyQ策略模式) <br /> TemplateMethodQ模板方法模式) <br /> VisitorQ访问者模式)<br /> <br /> <br /> <br /> Qhttp://www.javaeye.com/topic/41096<br /> <img src ="http://m.tkk7.com/shiliqiang/aggbug/289318.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://m.tkk7.com/shiliqiang/" target="_blank">矛_@</a> 2009-07-31 20:45 <a href="http://m.tkk7.com/shiliqiang/articles/289318.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>|络服务器的性能分析http://m.tkk7.com/shiliqiang/articles/288646.html矛_@矛_@Mon, 27 Jul 2009 14:13:00 GMThttp://m.tkk7.com/shiliqiang/articles/288646.htmlhttp://m.tkk7.com/shiliqiang/comments/288646.htmlhttp://m.tkk7.com/shiliqiang/articles/288646.html#Feedback0http://m.tkk7.com/shiliqiang/comments/commentRss/288646.htmlhttp://m.tkk7.com/shiliqiang/services/trackbacks/288646.html阅读全文

矛_@ 2009-07-27 22:13 发表评论
]]>
E序性能分析http://m.tkk7.com/shiliqiang/articles/288641.html矛_@矛_@Mon, 27 Jul 2009 13:37:00 GMThttp://m.tkk7.com/shiliqiang/articles/288641.htmlhttp://m.tkk7.com/shiliqiang/comments/288641.htmlhttp://m.tkk7.com/shiliqiang/articles/288641.html#Feedback0http://m.tkk7.com/shiliqiang/comments/commentRss/288641.htmlhttp://m.tkk7.com/shiliqiang/services/trackbacks/288641.htmlq篇文章主要是想谈谈在以CPUZ心的计算体系l构中媄响程序性能的主要因素和性能的分析方法以及多U程对程序性能的媄响,读这文章首先要具备一定的体系l构和操作系l基Q特别是q程调度Q徏议看?font face="Times New Roman">Operation System Concept》(中文《操作系l概论》)?/font>
 
先定义一下程序的性能Q就是在单位旉内能执行的Q务数或者执行某个Q务需要的旉。显Ӟ在更短的旉内执行更多的d性能p高?/font>
 

CPU?font face="Times New Roman">IO操作

 
a归正传,先看一个经典的入门?font face="Times New Roman">CE序Hello World!

int main(int argc, char * args[])
{
    int m = 0;
    for (int i = 0;i < 10;i ++)
    {
        m = m+i;
    }

    printf("Hello World! 1+2+…+10=%d\n", m);

    return 0;
}

q个不算原始的经典的Hello WorldQ比那个Hello WorldE微复杂了点Q加了一个@环,用来计算1+2+3…+10的倹{?/font>
 
如果有了操作pȝq程调度的基Q可以知道这个程序分成两D|行,W一D|计算1+2+…+10的|主要?font face="Times New Roman">CPUQ中央处理单元)中进行,C代码Q?/font>

    int m = 0;
    for (int i = 0;i < 10;i ++)
    {
        m = m+i;
    }

W二D|计结果输出到控制台的q段Q将一串文本通过昑֍驱动Q传送到昄器上昄Q主要在昑֍上进行,C代码Q?/font>

printf("Hello World! 1+2+…+10=%d\n", m);

整个E序序执行Q所?font face="Times New Roman">CPU先计完成得?font face="Times New Roman">1+2+…+10的值后Q将q个D{换成一串字W串Q然后将字符串发送给昄器,{待昄器显C完成后Q整个程序结束,如果?font face="Times New Roman">CPU执行表示Ԍ显C器执行表示为红Ԍ那么E序执行程如下Q?/font>
500)this.width=500;" border="0">
?font face="Times New Roman">1
假设CPU中计?font face="Times New Roman">1+2+…+10和将q个值变成字W串p?font face="Times New Roman">11nsQ纳U)Q而显卡将字符串显C到昄器上p?font face="Times New Roman">7nsQ那么整个程序运行花费了18ns?/font>
 
Hello World是最单的E序Q也是所有其他程序的基础Q在?font face="Times New Roman">CPUZ心的计算机结构中Q内存负责程序的存储Q?font face="Times New Roman">CPU负责E序的运和程控制Q其他元件被看成跟上?font face="Times New Roman">Hello World中显卡类似的外围讑֤Q也被称?font face="Times New Roman">IO讑֤Q所以Q何程序都可以看作是一pdCPU操作和一pdIO操作的符合体Q如下图所C:
500)this.width=500;" border="0" width="500">
?font face="Times New Roman">2
所以媄响程序性能的主要因素有两个斚wQ一?font face="Times New Roman">CPU操作的快慢,二是IO操作的快慢?/strong>
 
所以程序性能分析的主要方法就是正区分哪些是CPU操作Q哪些是IO操作?/strong>
 
CPU操作通常有这些:
赋值和计算Q如Q?font face="Times New Roman">m = i*j;
程控制Q如Q?font face="Times New Roman">while(true) { i ++;}
 
IO操作通常有这些:
盘文g操作?/font>
|络操作?/font>
键盘和鼠标操作?/font>
昑֍操作Q如在屏q上l图Q显C文本等?/font>
USB操作?/font>
串口操作?/font>
U外U操作?/font>
带机操作?/font>
通常?font face="Times New Roman">CPU和内存外的其他设备都可以看成IO操作Q内存之所以不看作IO讑֤Q是因ؓ内存讉K相对IO而言Q通常要快几个数量U,所以像char * buff = new char[100];q样的操作通常也看?font face="Times New Roman">CPU操作?/font>
 
通过分析划分出程序的CPU操作?font face="Times New Roman">IO操作E序D后Q可以有针对性的q行优化?/strong>
 
对于CPU操作Q常用的提升性能的方法是优化计算和流E控制代码,如相乘计?font face="Times New Roman"> m = i * 8Q可以?font face="Times New Roman"> m = i << 3Q因Z操作比乘法操作速度快,通常在某U语a中都会讲到程序的优化Q就属于优化CPU操作速度?/font>
对于IO操作Q如?font face="Times New Roman">IO操作q于频繁而成为系l瓶颈,可以清除一些不必要?font face="Times New Roman">IO操作Q也可以更换速度更快?font face="Times New Roman">IO讑֤来提高速度Q如把硬盘从5400转提升到7200转?/font>
 

多线E?/font>

 
下面看看多线E对E序性能的媄响,什么时候该使用多线E,什么时候用多U程达不到预期的效果?/font>
多线E是E序里面有像上面那样的多个执行流E,q些执行程独立或者联合v来完成某些Q务?/font>
先看看计机只有一?font face="Times New Roman">CPUQ一?font face="Times New Roman">IO讑֤Q程序有两个U程Q两个线E执行同L代码Q可以画出执行流E:
500)this.width=500;" border="0" width="500">
?font face="Times New Roman">3
U程1按正常的执行程执行Q线E?font face="Times New Roman">2虽然跟线E?font face="Times New Roman">1执行同样的代码,却出现很多不q箋的片D,比如2.2à2.3?font face="Times New Roman">2.4à2.5Q这是因为只有一?font face="Times New Roman">CPUQ所?font face="Times New Roman">CPU在进行线E?font face="Times New Roman">1?font face="Times New Roman">CPU操作Ӟ不能同时q行U程2?font face="Times New Roman">CPU操作Q也是2.4?font face="Times New Roman">2.5本来是跟U程1?font face="Times New Roman">1.3代码一P但是却被CPU分两ơ执行,因ؓCPU正在执行1.7?font face="Times New Roman">2.2?font face="Times New Roman">2.3也是同样的道理,因ؓIO讑֤要执?font face="Times New Roman">1.4的代码,所?font face="Times New Roman">2.2?font face="Times New Roman">2.3被打断。但是两个线E的CPU操作?font face="Times New Roman">IO操作在时间上可以重叠Q因Z们是不同的设备?/font>
也就是在旉上,CPU?font face="Times New Roman">IO讑֤只能同时做一件事情,CPU?font face="Times New Roman">IO讑֤可以各自做自q事情?/strong>
考察一U极端的情况Q假设某个程序没?font face="Times New Roman">IO操作Q只?font face="Times New Roman">CPU操作Q那么流E图变成Q?/font>
500)this.width=500;" border="0" width="500">
?font face="Times New Roman">4
U程1占用所有的CPU旉Q线E?font face="Times New Roman">2一直等待直到线E?font face="Times New Roman">1完成Q因为线E?font face="Times New Roman">1完成d后,依然可以再次执行dQ所以这时用线E?font face="Times New Roman">1完成d和用线E?font face="Times New Roman">2完成d没有区别Q也是U程2的存在ƈ不会让程序多完成一些Q务,所以线E?font face="Times New Roman">2的存在,q不能提升程序性能?/font>
所以,如果一个程序只?font face="Times New Roman">CPU操作Q那么多U程q不能提升程序性能?/strong>
同理Q如果一个程序只?font face="Times New Roman">IO操作Q那么多U程q不能提升程序性能?/strong>
 
但是多线E在现实中确实有提高E序性能的时候,那是因ؓ实际的程序像?font face="Times New Roman">3那样Q有CPU操作?font face="Times New Roman">IO操作l成Q?font face="Times New Roman">CPU操作?font face="Times New Roman">IO操作在时间上可以重叠Q所以,同一旉内,E序可以做更多的事情?/font>
如果一个线E中CPU操作旉?font face="Times New Roman">MQ?font face="Times New Roman">IO操作旉?font face="Times New Roman">NQ那么在单位旉内,q_?font face="Times New Roman">M/(M+N)在处?font face="Times New Roman">CPU操作Q有N/(M+N)的时?font face="Times New Roman">CPUI闲Q如果要?font face="Times New Roman">CPU充分利用Q那么可以增?font face="Times New Roman">(N/(M+N))/(M/(M+N))=N/M个线E来填补CPU操作的空白,q样CPU?font face="Times New Roman">100%被利用,如果U程再增加,CPU没有I闲Q几乎不会增加程序性能?/font>
所以,?font face="Times New Roman">CPU 100%利用的线E最大数?font face="Times New Roman">1+N/M?/strong>
同你Q让IO讑֤100%利用的线E最大数?font face="Times New Roman">1+M/N?/strong>
q两个公式只是一个度量式Q不是一个计式Q因为随着U程数的增加Q?font face="Times New Roman">CPU操作旉?font face="Times New Roman">IO操作旉会随着变化Q?font face="Times New Roman">M?font face="Times New Roman">N不再固定?/font>
 
看两U常用的E序Q服务器E序和用户交互程序?/font>
服务器程序通常提供某种|络服务Q如WEB服务器,q种E序要求能最大化的利?font face="Times New Roman">CPU?font face="Times New Roman">IOQ在单位旉内处理尽可能多的dQ所以应该用尽可能?font face="Times New Roman">CPU?font face="Times New Roman">IO都满W合工作Q多U程数可以取1+N/M?font face="Times New Roman">1+M/N中较的|如果观察服务器的CPU?font face="Times New Roman">IO使用率,会发C们常常接q?font face="Times New Roman">90%?/font>
用户交互E序通常Ҏ用户的某些输入进行相应的操作Q操作完成再ơ等待用戯入,?font face="Times New Roman">Microsoft WordQ要求对用户的输入能及时反应Q所以操作线E的CPU操作?font face="Times New Roman">IO操作应该有一定的I闲Q得用戯入线E能随时获取CPU来响应用L输入Q?font face="Times New Roman">Microsoft WindowsӞ打开d理器,可以发现CPU使用率常常很低,?font face="Times New Roman">1%?font face="Times New Roman">20%?/font>
 

IO复用

 
从上面的分析可以看出Q多U程提升E序性能Q主要得益于?font face="Times New Roman">CPU?font face="Times New Roman">IO讑֤能ƈ行操作,另一U让CPU?font face="Times New Roman">IO讑֤q行操作的方法是IO复用Q基本的思想是需要进?font face="Times New Roman">IO操作Ӟ只是发送一?font face="Times New Roman">IO操作hl?font face="Times New Roman">IO讑֤而不必等?font face="Times New Roman">IO完成Q?font face="Times New Roman">CPU操作可以l箋q行Q?font face="Times New Roman">IO操作完成后通过某种Ҏ如事仉知E序Q然后程序做相应的处理,程如下Q?/font>
500)this.width=500;" border="0" width="500">
?font face="Times New Roman">5
以前需?font face="Times New Roman">18ns执行的程序,现在只需?font face="Times New Roman">11ns可以完成,性能提升?/font>
常用的文件异步操作、网l异步操作都属于IO复用?/font>
使用IO复用后,E序通常只需要一个线E就可以完成所有的功能Q减操作系l线E间切换的开销Qƈ且不需要线E间同步Q但?font face="Times New Roman">IO复用需要用特定的Ҏ监视IO状态,开发相Ҏ较复杂?/font>
Window 2000?font face="Times New Roman">IOCPQ?font face="Times New Roman">IO Complete PortQ就是基?font face="Times New Roman">IO复用的思想?/font>

ȝ

虽然上面的结论是在一?font face="Times New Roman">CPUq且没有考虑操作pȝ的进E调度和内存理{因素的影响的前提下得出的,但是在以CPUZ心的计算Zpȝ构中Q?font face="Times New Roman">CPU操作?font face="Times New Roman">IO操作的划分确实普遍适用的,q程调度和内存管理本w也可以看成?font face="Times New Roman">CPU操作?font face="Times New Roman">IO操作复合的程序,对于?font face="Times New Roman">CPU的系l和?font face="Times New Roman">IO讑֤的系l,分析的基是所有这些设备能q行操作Q所以上面得出的l论是普遍适用的?/font>
在分析过E中Q对很多l论使用了粗体字Q是Z醒目Q不要死记硬背,要记住的是基本原理和分析ҎQ这h能放之四皆准?br />

转自Qhttp://blog.chinaunix.net/u1/52224/showart_417513.html


矛_@ 2009-07-27 21:37 发表评论
]]>文本处理(一)状态机(2) http://m.tkk7.com/shiliqiang/articles/286321.html矛_@矛_@Fri, 10 Jul 2009 13:16:00 GMThttp://m.tkk7.com/shiliqiang/articles/286321.htmlhttp://m.tkk7.com/shiliqiang/comments/286321.htmlhttp://m.tkk7.com/shiliqiang/articles/286321.html#Feedback0http://m.tkk7.com/shiliqiang/comments/commentRss/286321.htmlhttp://m.tkk7.com/shiliqiang/services/trackbacks/286321.htmlpȝE序员成长计?文本处理(一)

状态机(2)

o 用有IL态机解一道面试题?/p>

刚毕业的时候,我到一家外企面试,面试题里有这样一道题Q?/p>

l计一英文文章里的单词个数?/p>

有多U方法可以解q道题,q里我们选择用有IL态机来解Q做法如下:

先把q篇英文文章dC个缓冲区里,让一个指针从~冲区的头部一直移到缓冲区的尾部,指针会处于两U状态:“单词?#8221;?#8220;单词?#8221;Q加上后面提到的初始状态和接受状态,是有穷状态机的状态集。缓冲区中的字符集合是有穷状态机的字母表?/p>

如果当前状态ؓ“单词?#8221;Q移到指针时Q指针指向的字符是非单词字符(如标点和I格)Q那状态会?#8220;单词?#8221;转换?#8220;单词?#8221;。如果当前状态ؓ“? 词外”Q?Ud指针Ӟ指针指向的字W是单词字符(如字?Q那状态会?#8220;单词?#8221;转换?#8220;单词?#8221;。这些{换规则就是状态{换函数?/p>

指针指向~冲区的头部时是初始状态?/p>

指针指向~冲区的N时是接受状态?/p>

每次当状态从“单词?#8221;转换?#8220;单词?#8221;Ӟ单词计数增加一?br /> q个有穷状态机的图形表C如下:

下面我们看看E序怎么写:

int count_word(const char* text)

{

/*定义各种状态,我们不关心接受状态,q里可以不用定义?/

enum _State

{

STAT_INIT,

STAT_IN_WORD,

STAT_OUT_WORD,

}state = STAT_INIT;



int count = 0;

const char* p = text;



/*在一个@环中Q指针从~冲区头Ud~冲区尾*/

for(p = text; *p != '\0'; p++)

{

switch(state)

{

case STAT_INIT:

{

if(IS_WORD_CHAR(*p))

{

/*指针指向单词字符Q状态{换ؓ单词?/

state = STAT_IN_WORD;

}

else

{

/*指针指向非单词字W,状态{换ؓ单词?/

state = STAT_OUT_WORD;

}

break;

}

case STAT_IN_WORD:

{

if(!IS_WORD_CHAR(*p))

{

/*指针指向非单词字W,状态{换ؓ单词外,增加单词计数*/

count++;

state = STAT_OUT_WORD;

}

break;

}

case STAT_OUT_WORD:

{

if(IS_WORD_CHAR(*p))

{

/*指针指向单词字符Q状态{换ؓ单词?/

state = STAT_IN_WORD;

}

break;

}

default:break;

}

}



if(state == STAT_IN_WORD)

{

/*如果由单词内q入接受状态,增加单词计数*/

count++;

}



return count;

}

用状态机来解q道题目Q思\清晰Q程序简单,不易出错?/p>

q道题目只是Z展示一些奇技淫yQ还是有一些实际用处呢Q回{这个问题之前,我们先对上面的程序做Ҏ展,不只是统计单词的个数Q而且要分d里面的每个单词?/p>

int word_segmentation(const char* text, OnWordFunc on_word, void* ctx)

{

enum _State

{

STAT_INIT,

STAT_IN_WORD,

STAT_OUT_WORD,

}state = STAT_INIT;



int count = 0;

char* copy_text = strdup(text);

char* p = copy_text;

char* word = copy_text;



for(p = copy_text; *p != '\0'; p++)

{

switch(state)

{

case STAT_INIT:

{

if(IS_WORD_CHAR(*p))

{

word = p;

state = STAT_IN_WORD;

}

break;

}

case STAT_IN_WORD:

{

if(!IS_WORD_CHAR(*p))

{

count++;

*p = '\0';

on_word(ctx, word);

state = STAT_OUT_WORD;

}

break;

}

case STAT_OUT_WORD:

{

if(IS_WORD_CHAR(*p))

{

word = p;

state = STAT_IN_WORD;

}

break;

}

default:break;

}

}



if(state == STAT_IN_WORD)

{

count++;

on_word(ctx, word);

}



free(copy_text);



return count;

}

状态机不变Q只是在状态{换时Q做是事情不一栗这里从“单词?#8221;转换到其它状态时Q增加单词计敎ͼq分d当前的单词。至于拿分离出的单词来做什么,׃入的回调函数军_Q比如可以用来统计每个单词出现的频率?/p>

但如果讨是限于英文文章,q个E序的意义仍然不大,现在来做q一步扩展。我们考虑的文本不再是英文文章Q而是一些文本数据,q些数据׃些分隔符分开Q我们把数据UCؓtokenQ现在我们要把这些token分离出来?/p>

typedef void (*OnTokenFunc)(void* ctx, int index, const char* token);



#define IS_DELIM(c) (strchr(delims, c) != NULL)

int parse_token(const char* text, const char* delims, OnTokenFunc on_token, void* ctx)

{

enum _State

{

STAT_INIT,

STAT_IN,

STAT_OUT,

}state = STAT_INIT;



int count = 0;

char* copy_text = strdup(text);

char* p = copy_text;

char* token = copy_text;



for(p = copy_text; *p != '\0'; p++)

{

switch(state)

{

case STAT_INIT:

case STAT_OUT:

{

if(!IS_DELIM(*p))

{

token = p;

state = STAT_IN;

}

break;

}

case STAT_IN:

{

if(IS_DELIM(*p))

{

*p = '\0';

on_token(ctx, count++, token);

state = STAT_OUT;

}

break;

}

default:break;

}

}



if(state == STAT_IN)

{

on_token(ctx, count++, token);

}



on_token(ctx, -1, NULL);

free(copy_text);



return count;

}

用分隔符分隔的文本数据有很多Q如Q?/p>

环境PATHQ它?#8216;:’分开的多个\径组成。如Q?br /> /usr/lib/qt-3.3/bin:/usr/kerberos/bin:/backup/tools/jdk1.5.0_18/bin/:/usr/lib/ccache:/usr/local/bin:/bin:/usr/bin:/home/lixianjing/bin

文g名,它由‘/’分开的\径组成。如Q?br /> /usr/lib/qt-3.3/bin

URL中的参数Q它‘&’分开的多个key/value对组成?br /> hl=zh-CN&q=limodev&btnG=Google+搜烦&meta=&aq=f&oq=

所有这些数据都可以用上面的函数处理Q所以这个小函数是颇具实用h值的?/p>

矛_@ 2009-07-10 21:16 发表评论
]]>
文本处理(一)状态机(1)http://m.tkk7.com/shiliqiang/articles/286310.html矛_@矛_@Fri, 10 Jul 2009 11:37:00 GMThttp://m.tkk7.com/shiliqiang/articles/286310.htmlhttp://m.tkk7.com/shiliqiang/comments/286310.htmlhttp://m.tkk7.com/shiliqiang/articles/286310.html#Feedback0http://m.tkk7.com/shiliqiang/comments/commentRss/286310.htmlhttp://m.tkk7.com/shiliqiang/services/trackbacks/286310.htmlpȝE序员成长计?文本处理(一)

状态机(1)

o 有穷状态机的Ş式定?/p>

有穷状态机是一个五元组 (QQ?#931;Q?#948;Qq0QF)Q其中:
Q是一个有I集合,UCؓ状态集?br /> Σ是一个有I集合,UCؓ字母表?br /> δ: Q xΣQUCؓ状态{Ud数?br /> q0 是初始状态?br /> F 是接受状态集?/p>

教科书上是这样定义有I动机的,q个形式定义_的描qC有穷状态机的含义。但是大部分?包括我自?W一ơ看到它Ӟ反复的读上几遍,仍然不知道它在说什么。幸好通过一些实例,我们可以很容易明白有IL态机的原理?/p>

自动门是一个典型的有穷状态机Q?/p>

它有“开”?#8220;?#8221;两种状态,q就是它的状态集Q也是上面所说的Q?/p>

人可以从自动门进来或出去Q当来或出去的时候,自动门会自动打开Q如果在规定的时间内没有出,自动门会自动关上。h的进来、出d时三个事g是自动门的字母表Q也是上面所说的Σ。而自动门在当前状态下Q对事g的响应,会引L态的变化Q这是状态{换函敎ͼ也就是上面所说的δ?/p>

自动门刚安装好的时候,我们可以认ؓ它是关上的,所以关闭状态是自动门的初始状态?/p>

在理x况下Q自动门会一直运行,所以它没有接受状态,接受状态集F是空集?/p>

有穷状态机的Ş式定义很_Q文字描q比较通俗Q而图形表C则比较直观。通用建模语言QUMLQ里的状态图是状态机的常用图形表C方法。简单的状态图包括一些状态,用圆角方框表C,里面有状态的名称。状态之间的转换Q用头表示Q上面可以加转换条g。自动门的状态机可以用下图表C:

有穷状态机很简单,在生zM可以扑և很多q样的例子。但是教U书里讲得太复杂了,一会儿证明定性有IL态机和非定性有IL态机的等h,一会儿证明正则表达式的正则q算是封闭的Q一会儿又来个܇引理。花了很长时_我才明白q些原理Q但两年之后Q我又把它们忘得一q二净?/p>

主要原因是工作中没有Zq用它们Q这些理论的证明于编E没有太大用处,不过状态机本n却是文本处理利器Q由于程序员在很多场合下都是在与文本数据打交道,所以状态机是程序员必备的工具之一。这里我们将一起学习如何用状态机来处理文本数据,后面我们也会提到状态机的其它用途,不过不是本节的重炏V?br />


文章出处Q?a >http://www.limodev.cn/blog
作者联pL式:李先?<xianjimli at hotmail dot com>



矛_@ 2009-07-10 19:37 发表评论
]]>
文本处理(?http://m.tkk7.com/shiliqiang/articles/286307.html矛_@矛_@Fri, 10 Jul 2009 11:03:00 GMThttp://m.tkk7.com/shiliqiang/articles/286307.htmlhttp://m.tkk7.com/shiliqiang/comments/286307.htmlhttp://m.tkk7.com/shiliqiang/articles/286307.html#Feedback0http://m.tkk7.com/shiliqiang/comments/commentRss/286307.htmlhttp://m.tkk7.com/shiliqiang/services/trackbacks/286307.html
 

Builder模式

前面我们学习了状态机Qƈ利用它来解析各种格式的文本数据。解析过E把U性的文本数据转换成一些基本的逻辑单元Q但q通常只是d的一部分Q接下来我们q要对这些解析出来的数据q一步处理。对于特定格式的文本数据Q它的解析过E是一LQ但是对解析出来的数据的处理却是多种多样的。ؓ了让解析q程能被重用Q就需要把数据的解析和数据的处理分开?/p>

现在我们回过头来看一下前面写的函数parse_tokenQ这个函数把用分隔符分隔的文本数据,分离Z个一个的token?/p>

parse_token的函数原型如下:

typedef void (*OnTokenFunc)(void* ctx, int index, const char* token);
int parse_token(const char* text, const char* delims, OnTokenFunc on_token, void* ctx)

parse_token负责解析数据Q但它ƈ不关心数据代表的意义及用途。对数据的进一步处理由调用者提供的回调函数来完成,函数 parse_token每解析到一个tokenQ就调用q个回调函数。parse_token负责数据的解析,回调函数负责数据的处理,q样一来,数据的解析和数据的处理就分开了?/p>

parse_token可以认ؓ是Builder模式最朴素的应用。现在我们看看Builder 模式Q?/p>

Builder 模式的意图:一个复杂对象的构徏与它的表C分,使得同样的构E可以创Z同的表示?#8220;构徏”其实是前面的解析过E,?#8220;表示”是前面说的Ҏ据的处理?/p>

对象关系Q?br /> 对象关系
上面的parse_token与这里的Director对应?/p>

上面的回调函Cq里的Builder对应?/p>

具体的回调函Cq里的ConcreteBuilder对应?/p>

Ҏ据处理的l果是Product?/p>

对象协作Q?br /> 对象协作
Client是parse_token的调用者?/p>

׃parse_token是按面向q程的方式设计的Q所以ConcreteBuilder和Director的创建只是对应于一些初始化代码?/p>

调用parse_token相当于调用aDirector的Construct函数?/p>

调用回调函数相当于调用aConcreteBuilder的BuildPart函数?/p>

回调函数可能把处理结果存在它的参数ctx中,GetResult是从里面获取l果Q这是可选的q程Q依赖于具体回调函数所做的工作?/p>

parse_token的例子简单直接,对于理解Builder模式有较大的帮助Q不q毕竟它是面向过E的。现在我们以前面的XML解析器ؓ例来说明Builder模式Q虽然我们的代码是用C写的Q但完全是用面向对象的思想来设计的。Builder是一个接口,我们先把它定义出来:

struct _XmlBuilder;
typedef struct _XmlBuilder XmlBuilder;

typedef void (*XmlBuilderOnStartElementFunc)(XmlBuilder* thiz, const char* tag, const char** attrs);
typedef void (*XmlBuilderOnEndElementFunc)(XmlBuilder* thiz, const char* tag);
typedef void (*XmlBuilderOnTextFunc)(XmlBuilder* thiz, const char* text, size_t length);
typedef void (*XmlBuilderOnCommentFunc)(XmlBuilder* thiz, const char* text, size_t length);
typedef void (*XmlBuilderOnPiElementFunc)(XmlBuilder* thiz, const char* tag, const char** attrs);
typedef void (*XmlBuilderOnErrorFunc)(XmlBuilder* thiz, int line, int row, const char* message);
typedef void (*XmlBuilderDestroyFunc)(XmlBuilder* thiz);

struct _XmlBuilder
{
XmlBuilderOnStartElementFunc on_start_element;
XmlBuilderOnEndElementFunc on_end_element;
XmlBuilderOnTextFunc on_text;
XmlBuilderOnCommentFunc on_comment;
XmlBuilderOnPiElementFunc on_pi_element;
XmlBuilderOnErrorFunc on_error;
XmlBuilderDestroyFunc destroy;

char priv[1];
};

static inline void xml_builder_on_start_element(XmlBuilder* thiz, const char* tag, const char** attrs)

{

return_if_fail(thiz != NULL && thiz->on_start_element != NULL);

thiz->on_start_element(thiz, tag, attrs);

return;

}

static inline void xml_builder_on_end_element(XmlBuilder* thiz, const char* tag)

{

return_if_fail(thiz != NULL && thiz->on_end_element != NULL);

thiz->on_end_element(thiz, tag);

return;

}

...
(其它inline函数不列在这里了)

XmlBuilder接口要求实现下列函数Q?/p>

on_start_elementQ解析器解析C个v始TAG时调用它?br /> on_end_elementQ解析器解析C个结束TAG时调用它?br /> on_textQ解析器解析CD|本时调用它?br /> on_commentQ解析器解析C个注释时调用它?br /> on_pi_elementQ解析器解析C个处理指令时调用它?br /> on_errorQ解析器遇到错误时调用它?br /> destroyQ用销毁Builder对象?/p>

on_start_element和on_end_element{函数相当于Builder模式中的BuildPartX函数?/p>

XML解析器相当于DirectorQ在前面我们已经写好了,不过它对解析出来的数据没有做M处理。现在我们对它做些修改,让它调用XmlBuilder的函数?/p>

XML解析器对外提供下面几个函敎ͼ

o 构造函数?/p>

XmlParser* xml_parser_create(void);

o 为xmlParser讄builder对象?/p>

void       xml_parser_set_builder(XmlParser* thiz, XmlBuilder* builder);

o 解析XML

void       xml_parser_parse(XmlParser* thiz, const char* xml);

o 析构函数

void       xml_parser_destroy(XmlParser* thiz);

在解析时Q解析到相应的tagQ就调用XmlBuilder相应的函敎ͼ

o 解析到v始tag时调用xml_builder_on_start_element

static void xml_parser_parse_start_tag(XmlParser* thiz)
{
enum _State
{
STAT_NAME,
STAT_ATTR,
STAT_END,
}state = STAT_NAME;

char* tag_name = NULL;
const char* start = thiz->read_ptr - 1;

for(; *thiz->read_ptr != '\0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

switch(state)
{
case STAT_NAME:
{
if(isspace(c) || c == '>' || c == '/')
{
tag_name = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
state = (c != '>' && c != '/') ? STAT_ATTR : STAT_END;
}
break;
}
case STAT_ATTR:
{
xml_parser_parse_attrs(thiz, '/');
state = STAT_END;

break;
}
default:break;
}

if(state == STAT_END)
{
break;
}
}

tag_name = thiz->buffer + (size_t)tag_name;
/*解析完成Q调用builder的函数xml_builder_on_start_element?/
xml_builder_on_start_element(thiz->builder, tag_name, (const char**)thiz->attrs);

if(thiz->read_ptr[0] == '/')
{
/*如果tag?/'l束Q调用builder的函数xml_builder_on_end_element?/
xml_builder_on_end_element(thiz->builder, tag_name);
}

for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '\0'; thiz->read_ptr++);

return;
}

o 解析到结束tag时调用xml_builder_on_end_element

static void xml_parser_parse_end_tag(XmlParser* thiz)
{
char* tag_name = NULL;
const char* start = thiz->read_ptr;
for(; *thiz->read_ptr != '\0'; thiz->read_ptr++)
{
if(*thiz->read_ptr == '>')
{
tag_name = thiz->buffer + xml_parser_strdup(thiz, start, thiz->read_ptr-start);
/*解析完成Q调用builder的函数xml_builder_on_end_element?/
xml_builder_on_end_element(thiz->builder, tag_name);

break;
}
}

return;
}

o 解析到文本时调用xml_builder_on_text

static void xml_parser_parse_text(XmlParser* thiz)
{
const char* start = thiz->read_ptr - 1;
for(; *thiz->read_ptr != '\0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

if(c == '<')
{
if(thiz->read_ptr > start)
{
/*解析完成Q调用builder的函数xml_builder_on_text?/
xml_builder_on_text(thiz->builder, start, thiz->read_ptr-start);
}
thiz->read_ptr--;
return;
}
else if(c == '&')
{
xml_parser_parse_entity(thiz);
}
}

return;
}

o 解析到注释时调用xml_builder_on_comment

static void xml_parser_parse_comment(XmlParser* thiz)
{
enum _State
{
STAT_COMMENT,
STAT_MINUS1,
STAT_MINUS2,
}state = STAT_COMMENT;

const char* start = ++thiz->read_ptr;
for(; *thiz->read_ptr != '\0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

switch(state)
{
case STAT_COMMENT:
{
if(c == '-')
{
state = STAT_MINUS1;
}
break;
}
case STAT_MINUS1:
{
if(c == '-')
{
state = STAT_MINUS2;
}
else
{
state = STAT_COMMENT;
}
break;
}
case STAT_MINUS2:
{
if(c == '>')
{
/*解析完成Q调用builder的函数xml_builder_on_comment?/
xml_builder_on_comment(thiz->builder, start, thiz->read_ptr-start-2);
return;
}
}
default:break;
}
}

return;
}

o 解析到处理指令时调用xml_builder_on_pi_element

static void xml_parser_parse_pi(XmlParser* thiz)
{
enum _State
{
STAT_NAME,
STAT_ATTR,
STAT_END
}state = STAT_NAME;

char* tag_name = NULL;
const char* start = thiz->read_ptr;

for(; *thiz->read_ptr != '\0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

switch(state)
{
case STAT_NAME:
{
if(isspace(c) || c == '>')
{
tag_name = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
state = c != '>' ? STAT_ATTR : STAT_END;
}

break;
}
case STAT_ATTR:
{
xml_parser_parse_attrs(thiz, '?');
state = STAT_END;
break;
}
default:break;
}

if(state == STAT_END)
{
break;
}
}

tag_name = thiz->buffer + (size_t)tag_name;
/*解析完成Q调用builder的函数xml_builder_on_pi_element?/
xml_builder_on_pi_element(thiz->builder, tag_name, (const char**)thiz->attrs);

for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '\0'; thiz->read_ptr++);

return;
}

从上面的代码可以看出QXmlParser在适当的时候调用了XmlBuilder的接口函敎ͼ至于XmlBuilder在这些函数里做什么,要看具体的Builder实现了?/p>

先看一个最单的XmlBuilder实现Q它只是在屏q上打印Z递给它的数据Q?/p>

o 创徏函数

XmlBuilder* xml_builder_dump_create(FILE* fp)
{
XmlBuilder* thiz = (XmlBuilder*)calloc(1, sizeof(XmlBuilder));

if(thiz != NULL)
{
PrivInfo* priv = (PrivInfo*)thiz->priv;

thiz->on_start_element = xml_builder_dump_on_start_element;
thiz->on_end_element = xml_builder_dump_on_end_element;
thiz->on_text = xml_builder_dump_on_text;
thiz->on_comment = xml_builder_dump_on_comment;
thiz->on_pi_element = xml_builder_dump_on_pi_element;
thiz->on_error = xml_builder_dump_on_error;
thiz->destroy = xml_builder_dump_destroy;

priv->fp = fp != NULL ? fp : stdout;
}

return thiz;
}

和其它接口的创徏函数一P它只是把接口要求的函数指针指到具体的实现函数上?/p>

o 实现 on_start_element

static void xml_builder_dump_on_start_element(XmlBuilder* thiz, const char* tag, const char** attrs)
{
int i = 0;
PrivInfo* priv = (PrivInfo*)thiz->priv;
fprintf(priv->fp, "<%s", tag);

for(i = 0; attrs != NULL && attrs[i] != NULL && attrs[i + 1] != NULL; i += 2)
{
fprintf(priv->fp, " %s=\"%s\"", attrs[i], attrs[i + 1]);
}
fprintf(priv->fp, ">");

return;
}

o 实现on_end_element

static void xml_builder_dump_on_end_element(XmlBuilder* thiz, const char* tag)
{
PrivInfo* priv = (PrivInfo*)thiz->priv;
fprintf(priv->fp, "\n", tag);

return;
}

o 实现on_text

static void xml_builder_dump_on_text(XmlBuilder* thiz, const char* text, size_t length)
{
PrivInfo* priv = (PrivInfo*)thiz->priv;
fwrite(text, length, 1, priv->fp);

return;
}

o 实现on_comment

static void xml_builder_dump_on_comment(XmlBuilder* thiz, const char* text, size_t length)
{
PrivInfo* priv = (PrivInfo*)thiz->priv;
fprintf(priv->fp, "\n");

return;
}

o 实现on_pi_element

static void xml_builder_dump_on_pi_element(XmlBuilder* thiz, const char* tag, const char** attrs)
{
int i = 0;
PrivInfo* priv = (PrivInfo*)thiz->priv;
fprintf(priv->fp, "fp, " %s=\"%s\"", attrs[i], attrs[i + 1]);
}
fprintf(priv->fp, "?>\n");

return;
}

o 实现on_error

static void xml_builder_dump_on_error(XmlBuilder* thiz, int line, int row, const char* message)
{
fprintf(stderr, "(%d,%d) %s\n", line, row, message);

return;
}

上面的XmlBuilder实现单,而且有一定的实用价|我一般都会先写这样一个Builder。它不但对于调试E序有不的帮助Q而且只要E做修改Q就可以把它改进成一个美化数据格式的工P不管原始数据的格?当然要合W相应的语法规则)有多乱,你都能以一U比较好看的方式打印出来?/p>

下面我们再看一个比较复杂的XmlBuilder的实玎ͼ它根据接收的数据构徏一XML树?/p>

o 创徏函数

XmlBuilder* xml_builder_tree_create(void)
{
XmlBuilder* thiz = (XmlBuilder*)calloc(1, sizeof(XmlBuilder));

if(thiz != NULL)
{
PrivInfo* priv = (PrivInfo*)thiz->priv;

thiz->on_start_element = xml_builder_tree_on_start_element;
thiz->on_end_element = xml_builder_tree_on_end_element;
thiz->on_text = xml_builder_tree_on_text;
thiz->on_comment = xml_builder_tree_on_comment;
thiz->on_pi_element = xml_builder_tree_on_pi_element;
thiz->on_error = xml_builder_tree_on_error;
thiz->destroy = xml_builder_tree_destroy;

priv->root = xml_node_create_normal("__root__", NULL);
priv->current = priv->root;
}

return thiz;
}

和其它接口的创徏函数一P它只是把接口要求的函数指针指到具体的实现函数上。这里还创徏了一个根l点__root__Q以保证整棵树只有一个根l点?/p>

o 实现 on_start_element

static void xml_builder_tree_on_start_element(XmlBuilder* thiz, const char* tag, const char** attrs)
{
XmlNode* new_node = NULL;
PrivInfo* priv = (PrivInfo*)thiz->priv;

new_node = xml_node_create_normal(tag, attrs);
xml_node_append_child(priv->current, new_node);
priv->current = new_node;

return;
}

q里创徏了一个新的结点,q追加ؓpriv->current的子l点Q然后让priv->current指向新的l点?/p>

o 实现 on_end_element

static void xml_builder_tree_on_end_element(XmlBuilder* thiz, const char* tag)
{
PrivInfo* priv = (PrivInfo*)thiz->priv;
priv->current = priv->current->parent;
assert(priv->current != NULL);

return;
}

q里只是让priv->current指向它的父结炏V?/p>

o 实现 on_text

static void xml_builder_tree_on_text(XmlBuilder* thiz, const char* text, size_t length)
{
XmlNode* new_node = NULL;
PrivInfo* priv = (PrivInfo*)thiz->priv;

new_node = xml_node_create_text(text);
xml_node_append_child(priv->current, new_node);

return;
}

q里创徏一个文本结点, q追加ؓpriv->current的子l点?/p>

o 实现 on_comment

static void xml_builder_tree_on_comment(XmlBuilder* thiz, const char* text, size_t length)
{
XmlNode* new_node = NULL;
PrivInfo* priv = (PrivInfo*)thiz->priv;

new_node = xml_node_create_comment(text);
xml_node_append_child(priv->current, new_node);

return;
}

q里创徏一个注释结点, q追加ؓpriv->current的子l点?/p>

o 实现 on_pi_element

static void xml_builder_tree_on_pi_element(XmlBuilder* thiz, const char* tag, const char** attrs)
{
XmlNode* new_node = NULL;
PrivInfo* priv = (PrivInfo*)thiz->priv;

new_node = xml_node_create_pi(tag, attrs);
xml_node_append_child(priv->current, new_node);

return;
}

q里创徏一个处理指令结点, q追加ؓpriv->current的子l点?/p>

o 实现on_error

static void xml_builder_tree_on_error(XmlBuilder* thiz, int line, int row, const char* message)
{
fprintf(stderr, "(%d,%d) %s\n", line, row, message);

return;
}

下面我们再看XmlNode的数据结构和主要函数Q?/p>

o 数据l构

typedef struct _XmlNode
{
XmlNodeType type;
union
{
char* text;
char* comment;
XmlNodePi pi;
XmlNodeNormal normal;
}u;
struct _XmlNode* parent;
struct _XmlNode* children;
struct _XmlNode* sibling;
}XmlNode;

type军_了结点的cdQ可以是处理指o(XML_NODE_PI)、文?XML_NODE_TEXT)、注?XML_NODE_COMMENT)或普通TAG(XML_NODE_NORMAL)?/p>

联合体用于存攑օ体结点信息?/p>

parent指向父结炏V?/p>

children指向W一个子l点?/p>

sibling指向下一个兄弟结炏V?/p>

o 创徏普通TAGl点

XmlNode* xml_node_create_normal(const char* name, const char** attrs)
{
XmlNode* node = NULL;
return_val_if_fail(name != NULL, NULL);

if((node = calloc(1, sizeof(XmlNode))) != NULL)
{
int i = 0;
node->type = XML_NODE_NORMAL;
node->u.normal.name = strdup(name);

if(attrs != NULL)
{
for(i = 0; attrs[i] != NULL && attrs[i+1] != NULL; i += 2)
{
xml_node_append_attr(node, attrs[i], attrs[i+1]);
}
}
}

return node;
}

o 创徏处理指ol点

XmlNode* xml_node_create_pi(const char* name, const char** attrs)
{
XmlNode* node = NULL;
return_val_if_fail(name != NULL, NULL);

if((node = calloc(1, sizeof(XmlNode))) != NULL)
{
int i = 0;
node->type = XML_NODE_PI;
node->u.pi.name = strdup(name);
if(attrs != NULL)
{
for(i = 0; attrs[i] != NULL && attrs[i+1] != NULL; i += 2)
{
xml_node_append_attr(node, attrs[i], attrs[i+1]);
}
}
}

return node;
}

o 创徏文本l点

XmlNode* xml_node_create_text(const char* text)
{
XmlNode* node = NULL;
return_val_if_fail(text != NULL, NULL);

if((node = calloc(1, sizeof(XmlNode))) != NULL)
{
node->type = XML_NODE_TEXT;
node->u.text = strdup(text);
}

return node;
}

o 创徏注释l点

XmlNode* xml_node_create_comment(const char* comment)
{
XmlNode* node = NULL;
return_val_if_fail(comment != NULL, NULL);

if((node = calloc(1, sizeof(XmlNode))) != NULL)
{
node->type = XML_NODE_COMMENT;
node->u.comment = strdup(comment);
}

return node;
}

o q加一个兄弟结?/p>

XmlNode* xml_node_append_sibling(XmlNode* node, XmlNode* sibling)
{
return_val_if_fail(node != NULL && sibling != NULL, NULL);

if(node->sibling == NULL)
{
/*没有兄弟l点Q让兄弟l点指向sibling */
node->sibling = sibling;
}
else
{
/*否则Q把siblingq加为最后一个兄弟结?/
XmlNode* iter = node->sibling;
while(iter->sibling != NULL) iter = iter->sibling;
iter->sibling = sibling;
}
/*让兄弟结点的父结Ҏ向自q父结?/

sibling->parent = node->parent;

return sibling;
}

o q加一个子l点

XmlNode* xml_node_append_child(XmlNode* node, XmlNode* child)
{
return_val_if_fail(node != NULL && child != NULL, NULL);

if(node->children == NULL)
{
/*没有子结点,让子l点指向child */
node->children = child;
}
else
{
/*否则Q把child q加为最后一个子l点*/
XmlNode* iter = node->children;
while(iter->sibling != NULL) iter = iter->sibling;
iter->sibling = child;
}
/*让子l点的父l点指向自己*/

child->parent = node;

return child;
}

回头再看一下XmlParserQXmlBuilder及几个具体的XmlBuilder的实玎ͼ我们可以看到Q它们的实现都非常简单,其实q完全得益于Builder模式的设计方法。它利用分而治之的思想Q把数据的解析和数据的处理分开Q降低了实现的复杂度。其ơ它利用了抽象的思想Q从而数据的解析只关心处理数据处理的接口Q而不兛_的它的实玎ͼ使得数据解析和数据处理可以独立变化?/p>

分而治之和抽象是降低复杂度最有效的手D之一Q它们在Builder模式里得C很好的体现。初学者应该多׃旉M会?br />

文章出处Q?a >http://www.limodev.cn/blog
作者联pL式:李先?<xianjimli at hotmail dot com>



矛_@ 2009-07-10 19:03 发表评论
]]>
文本处理(一)http://m.tkk7.com/shiliqiang/articles/286306.html矛_@矛_@Fri, 10 Jul 2009 10:57:00 GMThttp://m.tkk7.com/shiliqiang/articles/286306.htmlhttp://m.tkk7.com/shiliqiang/comments/286306.htmlhttp://m.tkk7.com/shiliqiang/articles/286306.html#Feedback0http://m.tkk7.com/shiliqiang/comments/commentRss/286306.htmlhttp://m.tkk7.com/shiliqiang/services/trackbacks/286306.html文章出处Q?a >http://www.limodev.cn/blog
作者联pL式:李先?<xianjimli at hotmail dot com>

pȝE序员成长计?文本处理(一)

状态机(4)

XML解析?/p>

XMLQExtensible Markup LanguageQ即可扩展标记语aQ也是一U常用的数据文g格式。相对于INI来说Q它要复杂得多,INI只能保存U性结构的数据Q而XML可以保存树Şl构的数据。先看下面的例子Q?/p>

<?xml version="1.0" encoding="utf-8"?>
<mime-type xmlns="http://www.freedesktop.org/standards/shared-mime-info" type="all/all">
<!--Created automatically by update-mime-database. DO NOT EDIT!-->
<comment>all files and folders</comment>
</mime-type>

W一行称为处理指?PI)Q是l解析器用的。这里告诉解析器Q当前的XML文g遵@XML 1.0规范Q文件内容用UTF-8~码?/p>

W二行是一个v始TAGQTAG的名UCؓmime-type。它有两个属性,W一个属性的名称为xmlnsQgؓ http://www.freedesktop.org/standards/shared-mime-info。第二个属性的名称为typeQgؓ all/all?/p>

W三行是一个注释?/p>

W四行包括一个v始TAGQ一D|本和l束TAG?/p>

W五行是一个结束TAG?/p>

XML本n的格式不是本文的重点Q我们不详细讨论了。这里的重点是如何用状态机解析格式复杂的数据?/p>

按照前面的方法,先把数据dC个缓冲区中,让一个指针指向缓冲区的头部,然后Ud指针Q直到指向缓冲区的尾部。在q个q程中,指针可能指向Qv始TAGQ结束TAGQ注释,处理指o和文本。由此我们定义出状态机的主要状态:

1. 起始TAG状?br /> 2. l束TAG状?br /> 3. 注释状?br /> 4. 处理指o状?br /> 5. 文本状?/p>

׃起始TAG、结束TAG、注释和处理指o都在字符‘<’?#8216;>’之间Q所以当d字符‘<’Ӟ我们q无法知道当前的状态,Z便于处理Q我们引入一个中间状态,UCؓ“于号之?#8221;的状态。在d字符‘<’?#8216;!’之后Q还要读入两?#8216;-’Q才能确定进入注释状态,Z便于处理Q再引入两个中间状?#8220;注释前一”?#8220;注释前二”。再引入一?#8220;I?#8221;状态,表示不在上述M状态中?/p>

状态{换函敎ͼ
1. ?#8220;I?#8221;状态下Q读入字W?#8216;<’Q进?#8220;于号之?#8221;状态?br /> 2. ?#8220;I?#8221;状态下Q读入非‘<’非空白的字符Q进?#8220;文本”状态?br /> 3. ?#8220;于号之?#8221;状态下Q读入字W?#8216;Q?#8217;Q进?#8220;注释前一” 状态?br /> 4. ?#8220;于号之?#8221;状态下Q读入字W?#8216;?’Q进?#8220;处理指o”状态?br /> 5. ?#8220;于号之?#8221;状态下Q读入字W?#8216;/’Q进?#8220;l束TAG”状态?br /> 6. ?#8220;于号之?#8221;状态下Q读入有效的ID字符Q进?#8220;起始TAG”状态?br /> 7. ?#8220;注释前一” 状态下Q读入字W?#8216;-’Q?q入“注释前二” 状态?br /> 8. ?#8220;注释前二” 状态下Q读入字W?#8216;-’Q?q入“注释” 状态?br /> 9. ?“起始TAG” 状态?#8220;l束TAG” 状??#8220;文本” 状态?#8220;注释”状??#8220;处理指o”状态结束后Q重新回?#8220;I?#8221;状态下?/p>

q个状态机的图形表C如下:

下面我们来看看代码实玎ͼ

void xml_parser_parse(XmlParser* thiz, const char* xml)
{
/*定义状态的枚D?/
enum _State
{
STAT_NONE,
STAT_AFTER_LT,
STAT_START_TAG,
STAT_END_TAG,
STAT_TEXT,
STAT_PRE_COMMENT1,
STAT_PRE_COMMENT2,
STAT_COMMENT,
STAT_PROCESS_INSTRUCTION,
}state = STAT_NONE;

thiz->read_ptr = xml;
/*指针从头Ud到尾*/
for(; *thiz->read_ptr != '\0'; thiz->read_ptr++)
{
char c = thiz->read_ptr[0];

switch(state)
{
case STAT_NONE:
{
if(c == '<')
{
/*?#8220;I?#8221;状态下Q读入字W?#8216;<’Q进?#8220;于号之?#8221;状态?/
xml_parser_reset_buffer(thiz);
state = STAT_AFTER_LT;
}
else if(!isspace(c))
{
/*?#8220;I?#8221;状态下Q读入非‘<’非空白的字符Q进?#8220;文本”状态?/
state = STAT_TEXT;
}
break;
}
case STAT_AFTER_LT:
{
if(c == '?')
{
/*?#8220;于号之?#8221;状态下Q读入字W?#8216;?’Q进?#8220;处理指o”状态?/
state = STAT_PROCESS_INSTRUCTION;
}
else if(c == '/')
{
/*?#8220;于号之?#8221;状态下Q读入字W?#8216;/’Q进?#8220;l束TAG”状态?/
state = STAT_END_TAG;
}
else if(c == '!')
{
/*?#8220;于号之?#8221;状态下Q读入字W?#8216;Q?#8217;Q进?#8220;注释前一” 状?/
state = STAT_PRE_COMMENT1;
}
else if(isalpha(c) || c == '_')
{
/*?#8220;于号之?#8221;状态下Q读入有效的ID字符Q进?#8220;起始TAG”状态?/
state = STAT_START_TAG;
}
else
{
}
break;
}
case STAT_START_TAG:
{
/*q入子状?/
xml_parser_parse_start_tag(thiz);
state = STAT_NONE;
break;
}
case STAT_END_TAG:
{
/*q入子状?/
xml_parser_parse_end_tag(thiz);
state = STAT_NONE;
break;
}
case STAT_PROCESS_INSTRUCTION:
{
/*q入子状?/
xml_parser_parse_pi(thiz);
state = STAT_NONE;
break;
}
case STAT_TEXT:
{
/*q入子状?/
xml_parser_parse_text(thiz);
state = STAT_NONE;
break;
}
case STAT_PRE_COMMENT1:
{
if(c == '-')
{
/*?#8220;注释前一” 状态下Q读入字W?#8216;-’Q?q入“注释前二” 状态?/
state = STAT_PRE_COMMENT2;
}
else
{
}
break;
}
case STAT_PRE_COMMENT2:
{
if(c == '-')
{
/*?#8220;注释前二” 状态下Q读入字W?#8216;-’Q?q入“注释” 状态?/
state = STAT_COMMENT;
}
else
{
}
}
case STAT_COMMENT:
{
/*q入子状?/
xml_parser_parse_comment(thiz);
state = STAT_NONE;
break;
}
default:break;
}

if(*thiz->read_ptr == '\0')
{
break;
}
}

return;
}

解析q没有在此结束,原因是像“起始TAG”状态和“处理指o”状态等Q它们不是原子的Q内部还包含一些子状态,如TAG名称Q属性名和属性值等Q它们需要进一步分解。在考虑子状态时Q我们可以忘掉它所处的上下文,只考虑子状态本w,q样问题会得到简化。下面看一下v始TAG的状态机?/p>

假设我们要解析下面这样一个v始TAGQ?br /> <mime-type xmlns=”http://www.freedesktop.org/standards/shared-mime-info” type=”all/all”>

我们应该怎样d呢?q是按前面的ҎQ让一个指针指向缓冲区的头部,然后Ud指针Q直到指向缓冲区的尾部。在q个q程中,指针可能指向QTAG名称Q属性名和属性倹{由此我们可以定义出状态机的主要状态:

1. “TAG名称”状?br /> 2. “属性名”状?br /> 3. “属性?#8221;状?/p>

Z方便处理Q再引两个中间状态,“属性名之前”状态和“属性g?#8221;状态?/p>

状态{换函敎ͼ

初始状态ؓ“TAG名称”状?br /> 1. ?#8220;TAG名称”状态下Q读入空白字W,q入“属性名之前”状态?br /> 2. ?#8220;TAG名称”状态下Q读入字W?#8216;/’?#8216;>’Q进?#8220;l束”状态?br /> 3. ?#8220;属性名之前”状态下Q读入其它非I白字符Q进?#8220;属性名”状态?br /> 4. ?#8220;属性名”状态下Q读入字W?#8216;=’Q进?#8220;属性g?#8221;状态?br /> 5. ?#8220;属性g?#8221;状态下Q读入字W?#8216;“’Q进?#8220;属性?#8221;状态?br /> 6. ?#8220;属性?#8221;状态下Q读入字W?#8216;”’Q成功解析属性名和属性|回到“属性名之前”状态?br /> 7. ?#8220;属性名之前”状态下Q读入字W?#8216;/’?#8216;>’Q进?#8220;l束”状态?/p>

׃处理指o(PI)里也包含了属性状态,Z重用属性解析的功能Q我们把属性的状态再提取Z个子状态。这P“起始TAG”状态的囑Ş表示如下Q?br />

下面我们看代码实玎ͼ

static void xml_parser_parse_attrs(XmlParser* thiz, char end_char)
{
int i = 0;
enum _State
{
STAT_PRE_KEY,
STAT_KEY,
STAT_PRE_VALUE,
STAT_VALUE,
STAT_END,
}state = STAT_PRE_KEY;

char value_end = '\"';
const char* start = thiz->read_ptr;

thiz->attrs_nr = 0;
for(; *thiz->read_ptr != '\0' && thiz->attrs_nr < MAX_ATTR_NR; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

switch(state)
{
case STAT_PRE_KEY:
{
if(c == end_char || c == '>')
{
/*?#8220;属性名之前”状态下Q读入字W?#8216;/’?#8216;>’Q进?#8220;l束”状态?/
state = STAT_END;
}
else if(!isspace(c))
{
/*?#8220;属性名之前”状态下Q读入其它非I白字符Q进?#8220;属性名”状态?/
state = STAT_KEY;
start = thiz->read_ptr;
}
}
case STAT_KEY:
{
if(c == '=')
{
/*?#8220;属性名”状态下Q读入字W?#8216;=’Q进?#8220;属性g?#8221;状态?/
thiz->attrs[thiz->attrs_nr++] = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
state = STAT_PRE_VALUE;
}

break;
}
case STAT_PRE_VALUE:
{
/*?#8220;属性g?#8221;状态下Q读入字W?#8216;“’Q进?#8220;属性?#8221;状态?/
if(c == '\"' || c == '\'')
{
state = STAT_VALUE;
value_end = c;
start = thiz->read_ptr + 1;
}
break;
}
case STAT_VALUE:
{
/*?#8220;属性?#8221;状态下Q读入字W?#8216;”’Q成功解析属性名和属性|回到“属性名之前”状态?/
if(c == value_end)
{
thiz->attrs[thiz->attrs_nr++] = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
state = STAT_PRE_KEY;
}
}
default:break;
}

if(state == STAT_END)
{
break;
}
}

for(i = 0; i < thiz->attrs_nr; i++)
{
thiz->attrs[i] = thiz->buffer + (size_t)(thiz->attrs[i]);
}
thiz->attrs[thiz->attrs_nr] = NULL;

return;
}

记得在XML里,单引号和双引号都可以用来界定属性|所以上面对此做了特D处理?/p>

static void xml_parser_parse_start_tag(XmlParser* thiz)
{
enum _State
{
STAT_NAME,
STAT_ATTR,
STAT_END,
}state = STAT_NAME;

char* tag_name = NULL;
const char* start = thiz->read_ptr - 1;

for(; *thiz->read_ptr != '\0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

switch(state)
{
case STAT_NAME:
{
/*?#8220;TAG名称”状态下Q读入空白字W,属性子状态?/
/*?#8220;TAG名称”状态下Q读入字W?#8216;/’?#8216;>’Q进?#8220;l束”状态?/
if(isspace(c) || c == '>' || c == '/')
{
state = (c != '>' && c != '/') ? STAT_ATTR : STAT_END;
}
break;
}
case STAT_ATTR:
{
/*q入“属?#8221;子状?/
xml_parser_parse_attrs(thiz, '/');
state = STAT_END;

break;
}
default:break;
}

if(state == STAT_END)
{
break;
}
}

for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '\0'; thiz->read_ptr++);

return;
}

处理指o的解析和起始TAG的解析基本上是一LQ这里只是看一下代码:

static void xml_parser_parse_pi(XmlParser* thiz)
{
enum _State
{
STAT_NAME,
STAT_ATTR,
STAT_END
}state = STAT_NAME;

char* tag_name = NULL;
const char* start = thiz->read_ptr;

for(; *thiz->read_ptr != '\0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

switch(state)
{
case STAT_NAME:
{
/*?#8220;TAG名称”状态下Q读入空白字W,属性子状态?/
/*?#8220;TAG名称”状态下Q?#8216;>’Q进?#8220;l束”状态?/
if(isspace(c) || c == '>')
{
state = c != '>' ? STAT_ATTR : STAT_END;
}

break;
}
case STAT_ATTR:
{
/*q入“属?#8221;子状?/
xml_parser_parse_attrs(thiz, '?');
state = STAT_END;
break;
}
default:break;
}

if(state == STAT_END)
{
break;
}
}

tag_name = thiz->buffer + (size_t)tag_name;

for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '\0'; thiz->read_ptr++);

return;
}

注释Q结束TAG和文本的解析非常单,q里l合代码看看p了:

“注释”子状态的处理Q?/p>

static void xml_parser_parse_comment(XmlParser* thiz)
{
enum _State
{
STAT_COMMENT,
STAT_MINUS1,
STAT_MINUS2,
}state = STAT_COMMENT;

const char* start = ++thiz->read_ptr;
for(; *thiz->read_ptr != '\0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

switch(state)
{
case STAT_COMMENT:
{
/*?#8220;注释”状态下Q读?#8216;-’Q进?#8220;减号一”状态?/
if(c == '-')
{
state = STAT_MINUS1;
}
break;
}
case STAT_MINUS1:
{
if(c == '-')
{
/*?#8220;减号一”状态下Q读?#8216;-’Q进?#8220;减号?#8221;状态?/
state = STAT_MINUS2;
}
else
{
state = STAT_COMMENT;
}
break;
}
case STAT_MINUS2:
{
if(c == '>')
{
/*?#8220;减号?#8221;状态下Q读?#8216;>’Q结束解析?/
return;
}
else
{
state = STAT_COMMENT;
}
}
default:break;
}
}

return;
}

“l束TAG”子状态的处理Q?/p>

static void xml_parser_parse_end_tag(XmlParser* thiz)
{
char* tag_name = NULL;
const char* start = thiz->read_ptr;
for(; *thiz->read_ptr != '\0'; thiz->read_ptr++)
{
/*d‘>’Q结束解析?/
if(*thiz->read_ptr == '>')
{
break;
}
}

return;
}

“文本”子状态的处理Q?/p>

static void xml_parser_parse_text(XmlParser* thiz)
{
const char* start = thiz->read_ptr - 1;
for(; *thiz->read_ptr != '\0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;
/*d‘>’Q结束解析?/
if(c == '<')
{
if(thiz->read_ptr > start)
{
}
thiz->read_ptr--;
return;
}
else if(c == '&')
{
/*d‘&’Q进入实?entity)解析子状态?/
xml_parser_parse_entity(thiz);
}
}

return;
}

实体(entity)子状态比较简单,q里不做q一步分析了Q留l读者做l习?/p>

矛_@ 2009-07-10 18:57 发表评论
]]>
tomcat 在eclipse中的部vhttp://m.tkk7.com/shiliqiang/articles/282894.html矛_@矛_@Wed, 17 Jun 2009 10:07:00 GMThttp://m.tkk7.com/shiliqiang/articles/282894.htmlhttp://m.tkk7.com/shiliqiang/comments/282894.htmlhttp://m.tkk7.com/shiliqiang/articles/282894.html#Feedback0http://m.tkk7.com/shiliqiang/comments/commentRss/282894.htmlhttp://m.tkk7.com/shiliqiang/services/trackbacks/282894.htmlTomcat源码学习Q一Q?/a>

转自:http://carllgc.blog.ccidnet.com/blog-htm-do-showone-uid-4092-type-blog-itemid-263093.html

作ؓ一?span>JavaE序员,如果您没有接触过开源Y件、项目或框架的话Q恐怕有些不可思议。蘪轰烈烈的开源运动v源于Linux操作pȝQ?/span>Apache基金会在其中扮演了中砥q角色Q业界巨?/span>SUNQ?/span>IBMQ?/span> BEA ?/span>Oracle{公司的U极参与Q得声势浩大的开源运动成Y件开发领域势不可挡的力量?/span>2001q?/span>11月,IBM?/span>Apache基金会捐献出Visual Age for JavaQ这个看似穷途末路的产品l众多高手的攚w,演变煌一时的EclipseQ直接击败了不开源的JBuilderQ让做编译器起家?/span>Borland公司几乎兛_大吉?/span>Eclipseq个产品如此l典Q以至于微Y?/span>Visual Studio都得向它学习。在Apache Harmony的围q堵截下Q?/span>Java的发明?/span>Sun公司一看势头不妙,?/span>2006q宣?/span>Java开源,随后又公开了其旗舰U?/span>Solaris的源代码。今q?/span>1月,开源的d头、冷酯U的微Y也不得不?/span>MS-RL协议下公开.Net的源代码。但是,在这如火如荼的开源运动中Q我们中国的E序员又有多A献呢Q我们开创了哪些框架、项目和产品Qؓ开源界ȝ加瓦呢?以笔者短的目光看来Q我们对开源界贡献的东西恐怕很,能够与国外经典开源项目一较高下的Q少之又矣Q?/span>

作ؓ一名中国的E序员,׃能没有遗憑֐Qؓ什么经典的Apache Web Server不是中国人写的;Z?/span>Linus Torvalds在大学时代就写出Linuxq振臂一|应者云集;Z?/span>JBoss能与巨无霸式?/span>Websphere相抗衡;Z?/span>MySQL能在Oracle?/span>SQL Server的夹M发展q壮?/span>…… Q如此等{问题,在遗憾之余,我想我们应该q旉好好思考一下,中国的Y件业怎么了,中国的程序员又怎么啦?


在笔者看来,我们的程序员对开源的理解是相当狭隘的。国学大师王国维曾说q,古往今来成大学问大事业者要l历三种境界Q?#8220;昨夜襉K凋碧树,独上高楼Q望天涯\”Q这是第一重境界,qh也;“带渐宽l不悔,Z消得人憔?#8221;Q苦苦求索之境界也;W三重境界ؓ“众里M千百度,蓦然回首Q那人却在灯火阑珊处”Q经历多次的失败和挫折后,l于参透真谛,领悟真理。我觉得开源也有三重境界:


首先Q我们要敞开心胸Q拥抱开源(
Open to Open Source)。这重境界我们大安能做刎ͼ拿来M嘛,谁h不会。当我们的项目需要数据库Ӟ去下蝲一个免费MySQLQ需要IDEӞM? EclipseQ需要版本控制工hQ就M载CVSQ需要写搜烦引擎ӞLucene可能是我们的最爱;当我们开发J2EE Web应用ӞStruts/JSF加Hibernate/iBATIS再加上Spring或许成ؓ我们的首选架构。但是,我们l大部分E序员都停留在这 个层ơ上Q大家下载之后,看看文介绍Q安装、配|ƈ能运行,׃Z事大吉,一切顺利。偶遇C些问题,去Google一搜,{案立马可得?

其次Q我们要深入开源,了解开源(
Dig into Open Source)。要辑ֈq个层次Q就有些隑ֺ了。我们不但要知其Ӟq要知其所以然?#8220;知其所以然”的最好办法就是下载源代码Q仔l研读,揣摩q会源? 码的_义Q看看这些经q诸多高手修改的源代码究竟藏有什么玄机,我们能从其中学习到哪些设计思想及设计模式,能复用其中哪些源代码Qh家运用了哪些软g? 理思想把这些来自世界各地程序员的劳动汇集成一个品,代码架构如何QY仉|管理又是怎样q行?#8230;…Q等{等{,我们从源代码中学习的东西太多了。在? L代码Ӟ我们要多问自己几个ؓ什么,q样׃收获更多?

再次Q我们要融入开源,贡献开源(
Get involved in Open Source)。当我们d理解该项目源代码后,我们应发挥一?#8220;Zh为我Q我Zh?#8221;的思想Q或l合您的实际需要,或结合您的新xQ或针对Mail lists上的问题Q对该开源项目加以改q和创新Qƈ把自q代码贡献出来Q让大家评估。当Ӟ如果您有好的xQ您完全可以创徏自己的开源项 目,Apache基金会中众多的开源项目不都是我们q大E序员一手创建的吗?但是Q在创徏新开源项目时Q切忌不要重新发明轮子?

W者才疏学,想以
Apache Jakarta目包中的核心项目TomcatZQ希望通过阅读源码Q能从这个经兔R目中学到更多的东西,为我们中国的开源事业v到抛砖引玉的作用?

下面我们开始我们的
Tomcat源码学习之旅?

1. 下蝲
Tomcat6.0的源代码

首先Q我们得下蝲Tomcat6.0的源代码。Tomcat源代码的版本控制工具不是CVSQ而是SubversionQ如果您的机器上没有安装SubversionQ请?/span>
http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=91 下蝲q安装这个开源的版本控制工具。当Ӟ如果您想?/span>Eclipse中直接导?/span>Tomcat源代码,请从http://subclipse.tigris.org/update_1.0.x下蝲Subversion插gQ即可导?/span>Tomcat源代码。安装完成后Q请?/span>MS-DOSH口中键?/span>svn export helpQ您会看到Q?/span>

C:\Documents and Settings\carlwu>svn help export

export: 产生一个无版本控制的目录树副本?/span>

用法: 1?/span>export [-r REV] URL[@PEGREV] [PATH]

2?/span>export [-r REV] PATH1[@PEGREV] [PATH2]

1、从 URL 指定的仓库,导出一个干净的目录树?/span> PATH。如果有指定

REV 的话Q内容即版本的,否则是 HEAD 版本。如?/span> PATH

被省略的话,URL的最后部份会被用来当成本地的目录名称?/span>

2、在工作副本中,从指定的 PATH1 导出一个干净的目录树?/span> PATH2。如?/span>

有指?/span> REV 的话Q会从指定的版本导出Q否则从工作副本导出。如?/span>

PATH2 被省略的话,PATH1 的最后部份会被用来当成本地的目录名称?/span>

如果没有指定 REV 的话Q所有的本地修改都保留,但是未纳入版本控?/span>

的文件不会被复制?/span>



如果指定?/span> PEGREV Q将从指定的版本本开始查找?/span>

有效选项:。。。。。?/span>

我们看到Subversionl我们提供了非常友好的帮助,q且是中文的Q看来中国程序员对这个开源项目有所贡献。接下来Q请?/span>MS-DOS下键入:

svn export http://svn.apache.org/repos/asf/tomcat/tc6.0.x/tags/TOMCAT_6_0_0/ D:\carl_wu\tomcat\src\

q个命o的意思是?/span>Tomcat6.0的源代码?/span>Subversion库中导入到本机的D:\carl_wu\tomcat\src\目录Q命令运行后Q您E等几分钟,׃看到Tomcat的源代码利导入到目标目录。下面是源代码的目录机构Q从q个目录l构中,我们可以看出该项目的开发者用的IDE?/span>EclipseQ因为我们看C熟悉?/span>.project?/span>.classpath文g。如果您打算开发一?/span>Stand alone?/span>Java应用E序Q不妨借鉴一?/span>Tomcat的目录结构,把脚本文件放?/span>bin目录Q将xml?/span>properties配置文g攑֜conf目录中,?/span>Java源码文g攑֜java或?/span>src目录中,资源文g比如说图片文Ӟini文g及其它的一些静态资源文件可以放?/span>res目录Q测试源代码可以攑֜test目录中。这是一个典型的Java应用E序的目录机构,W者以前曾接触C个来自美国的产品Q其源代码目录结构和Tomcat及其相像?/span>






2. ~译q运?/span>

代码下蝲后,我们接下来就是要~译q运?/span>Tomcat。一提编译,我们不禁会想到可qAnt。不错,Tomcat正是?/span>Ant作ؓ~译工具Q如果您q没有安装,请从http://ant.apache.org/bindownload.cgi 处下载ƈ安装它。然后,请从Tomcat的源代码文g扑ֈbuild.properties.default文gQƈ该文g复制?/span>build.propertiesQ然后打开build.propertiesQ找C面这行:

base.path=/usr/share/java

它改ؓQ?/span>

base.path= D:/carl_wu/tomcat/share

?/span>Tomcat~译q程中,Ant会让我们下蝲一些必要的依赖目Q?/span>base.path目录是用来保存q些目文g的,我们可以这个属性指向一个已l存在的目录。修改完base.path后,我们回到MS-DOSH口Q切换到Tomcat源代码所在目录,然后q行ant download命oQ如下图所C:





一分钟未到Q?/span>Ant告诉我们一个错误ƈ提示我们~译p|Q具体错误信息如下:

downloadzip:

[get] Getting: http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip
[get] To: D:\carl_wu\tomcat\share\file.zip
[get] Error opening connection java.io.FileNotFoundException: http://sunsite.informatik.rwth-aachen.de:3080/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip

[get] Error opening connection java.io.FileNotFoundException: http://sunsite.informatik.rwth-aachen.de:3080/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip

[get] Error opening connection java.io.FileNotFoundException: http://sunsite.informatik.rwth-aachen.de:3080/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip

[get] Can't get http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip to D:\carl_wu\tomcat\share\file.zip

BUILD FAILED

D:\carl_wu\tomcat\src\build.xml:554: The following error occurred while executing this line:

D:\carl_wu\tomcat\src\build.xml:514: Can't get http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip to D:\carl_wu\tomcat\share\file.zip

Total time: 41 seconds

q个~译错误非常单,是找不?/span>http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip 文g。有人可能会惻ITomcat的编译和Eclipse?/span>JDT有什么关p?其实不然Q?/span>Tomcat是在Eclipse下开发的Q所以需?/span>Eclipse?/span>JDTQ?/span>Java Development toolingQ插件来~译Tomat源代码。既然找不到Q我们只好自己动手,?/span>Google一搜,马上发现q个文g的有效下载地址为:http://mirror.calvin.edu/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip。我?/span>打开刚才?/span>build.properties文gQ将?/span>34行修改ؓQ?/span>

jdt.loc= http://mirror.calvin.edu/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip

修改保存build.properties文g后,重新开?/span>ant downloadd。这ơ我们等的时间较长,因ؓeclipse-JDT-3.2.zip大约?/span>19MQ下载需要一D|间。我们可乘此ZL杯茶弄点咖啡什么的Q等我们品茶回来Q发现敬业的蚂蚁Ant告诉我们~译成功Q虽然编译器l出几个警告。这时我们可发现刚才创徏?/span>base.path目录Q?/span>D:\carl_wu\tomcat\shareQ中已经下蝲?/span>6个依赖项目,它们都是Tomcat~译所必须的?/span>

下面开始真正的~译d了,请在MS-DOSH口内键?/span>antq回车,Ant2分钟内编?/span>1000多个源文件ƈ?/span>Tomcat部v?/span>output目录。编译顺利完成后Q请打开Tomcat的源代码目录Q会发现多了一?/span>output目录Q这?/span>Ant的编译后的输出目录。请打开Tomcat源代码的output\build\bin子目录,双击startup.bat文gQ我们即可成功启?/span>Tomcat6.0Q此时我们的~译工作q利完成了?/span>


3. 导入源代码到Eclipse

3.1 h开EclipseQ新Z?/span>Java目Q然后点?#8220;Next”按钮Q请选择“Create project from existing source”Q?/span> q在Directory文本框内填入我们刚才下蝲?/span>Tomcat源代码目录(i.e. D:\carl_wu\tomcat\src)Q然后点?#8220;Next”直至l束?/span>




3.2 我们会看到Eclipse拒绝~译Q这是因?/span>Eclipse找不到该目指定的库文g。请叛_该项目,在弹单中选择“PropertiesàLibraries”Q然后删除两个以TOMCAT_LIBS开头的两个库文Ӟ只保留一?/span>JRE库文Ӟ然后点击“OK”按钮Q这?/span>Eclipse开始编?/span>Tomcat源代码,但是发现一堆错误,q是因ؓ我们没有目d~译所必须?/span>Jar包?/span>

3.3 准备?/span>Tomcat目所必须?/span>jar文gQ其实,刚才我们q行ant downloaddӞ已经下蝲q这?/span>jar文g包?/span>

ant.jar Q请?/span>ant安装目录?/span>lib子目录中拯Q?/span>

commons-collections-3.1.jar Q从刚才Ant下蝲?/span>commons-collections-3.1子目录中拯Q?/span>

commons-dbcp-1.2.1.jarQ从刚才Ant下蝲?/span>commons-dbcp-1.2.1子目录中拯Q?/span>

commons-logging-1.1.jarQ如果您本机没有q个jar包,请从http://commons.apache.org/downloads/download_logging.cgi处下载)

commons-pool-1.2.jarQ从刚才Ant下蝲?/span>commons-pool-1.2子目录中拯Q?/span>

org.eclipse.jdt.core_3.2.0.v_671.jarQ从刚才Ant下蝲?/span>eclipse\plugins子目录中拯Q?/span>

3.4 当我们准备好q些jar文g后,这些文件拷贝到某一目录Q比如说D:\carl_wu\tomcat\tomcat_lib目录Q,然后?/span>Eclipse中新Z?/span>User LibrariesQ我们将q个新徏?/span>User Libraries命名?/span>TOMCAT_LIBSQƈ把这些文件加?/span>TOMCAT_LIBS。然后将我们新徏?/span>TOMCAT_LIBSd?/span>Tomcat6目。另外,别忘了把JUnit库也加到Tomcat6目。这?/span>Eclipse开始重新编译,~译q程利通过Q所有错误均消失Q此?/span>Tomcat6目的目录结构如下:





q有Q请?/span>test目录也加入到源代码中Q方法是?/span>Eclipse中右?/span>”test”目录Q然后在弹出菜单中选择“Build path”à”Use as Source Folder”Q之后我们会看到test目录上就多了个源代码的符P如上图所C?/span>

3.5?/span>Eclipse中运?/span>Tomcat。请扑ֈTomcat的启动主c?/span>org.apache.catalina.startup.BootstrapQ右击这个类Q在弹出菜单中选择“Run As…”à”Open Run Dialog…”Q然后在弹出?#8220;Run”H口中填入程序运行参?#8220;start”?/span>JVMq行参数catalina.homeQ如下面H口所C:




然后点击“Run”按钮Q我们将会看?/span>Tomcat正常启动。恭喜,׃?/span>Tomcat源码已经成功导入EclipseQ这Ӟ可视化的UML分析工具?/span>Debug工具pz上用场了?/span>

3.5 调试TomcatQ请打开org.apache.jasper.compiler.Compilercȝ源代码,?/span>generateJava()Ҏ的第一行打一个断点,然后?/span>Eclipse的调试状态下q行TomcatQ等Tomcatq行后,打开我们的浏览器Q在地址栏中输入
http://localhost:8080/examples/jsp/jsp2/el/basic-comparisons.jspq回车,然后我们可观察到Eclipse此时切换臌试视图:







上面的小实验表明我们可以?/span>Eclipse中通过Debugger观察Tomcat的内部运行机理。另外补充一点,上面?/span>generateJavaҎ是将jsp动态编译至java classQ这个方法只是在W一ơ请求或?/span>Jsp源码发生变化时执行,如果您再ơ在览器中发送同LhQ您看不到上图?/span>Debug界面Q因Ҏ不再执行?/span>

另外Q还有一点很有意思?/span>Tomcat6以前版本的源代码分散在好几个子项目中Q他们分别叫?/span>jakarta-servletapi-5Q?/span>jakarta-tomcat-5Q?/span>jakarta-tomcat-catalinaQ?/span>jakarta-tomcat-connectors?/span>jakarta-tomcat-jasperQ我觉得Tomcat的开发者可能嫌q样做太ȝ了,所?/span>Tomcat6版本中将q些子项目都合ƈ在一起了。但是,q种做法不利于我们阅ȝ解源代码



矛_@ 2009-06-17 18:07 发表评论
]]>
վ֩ģ壺 ˾ҹѸ| vѹۿ| WWWƵ| ޾Ʒ߾ƷƵ| AVþþƷ| ޳?߹ۿ| պav| þþƷ| ˹վvƬѹۿ| ޹˾þۺ| ޲Ƶ߹ۿ| ޼Ůۺ99| ˾žŴɫ㽶վ| ۺ˾þôý| av붫˵| ѹۿվ߲| 91ɫۺϾþѷ| Ұ߹ۿƵ| yellowƵ߹ۿ| ŮƵaƵȫվһ| Ļ| jjzzjjzz߹ۿ| ޶·Ļ߿| Ů@TubeumTV| ۺС˵þ| avŮӰ| þþƷAVý | ޹Ʒ߹ۿ| ձ߿Ƭ| Ƶ߹ۿ| AVպAV| һ߹ۿ | պƷһ| ĻѸ| ˼˼99re66߾Ʒѹۿ| 99ƷƵѹۿ| 1000ҹ| պƵ| ѹۿ+ձ| ձɫѹۿ| ޹һǻ|