﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-海阔天空-文章分类-基础技术</title><link>http://www.blogjava.net/shiliqiang/category/40327.html</link><description>I'm on my way!</description><language>zh-cn</language><lastBuildDate>Mon, 10 Aug 2009 08:39:57 GMT</lastBuildDate><pubDate>Mon, 10 Aug 2009 08:39:57 GMT</pubDate><ttl>60</ttl><item><title>零拷贝技术与实现</title><link>http://www.blogjava.net/shiliqiang/articles/290421.html</link><dc:creator>石头@</dc:creator><author>石头@</author><pubDate>Sun, 09 Aug 2009 03:09:00 GMT</pubDate><guid>http://www.blogjava.net/shiliqiang/articles/290421.html</guid><wfw:comment>http://www.blogjava.net/shiliqiang/comments/290421.html</wfw:comment><comments>http://www.blogjava.net/shiliqiang/articles/290421.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shiliqiang/comments/commentRss/290421.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shiliqiang/services/trackbacks/290421.html</trackback:ping><description><![CDATA[<font size="3">一．基本概念<br />
零
拷贝（zero-copy）基本思想是：数据报从网络设备到用户程序空间传递的过程中，减少数据拷贝次数，减少系统调用，实现CPU的零参与，彻底消除
CPU在这方面的负载。实现零拷贝用到的最主要技术是DMA数据传输技术和内存区域映射技术。如图1所示，传统的网络数据报处理，需要经过网络设备到操作
系统内存空间，系统内存空间到用户应用程序空间这两次拷贝，同时还需要经历用户向系统发出的系统调用。而零拷贝技术则首先利用DMA技术将网络数据报直接
传递到系统内核预先分配的地址空间中，避免CPU的参与；同时，将系统内核中存储数据报的内存区域映射到检测程序的应用程序空间（还有一种方式是在用户空
间建立一缓存，并将其映射到内核空间，类似于linux系统下的kiobuf技术），检测程序直接对这块内存进行访问，从而减少了系统内核向用户空间的内
存拷贝，同时减少了系统调用的开销，实现了真正的&#8220;零拷贝&#8221;。<br />
<br />
<br />
图1 传统数据处理与零拷贝技术之比较<br />
二．实现<br />
在redhat7.3
上通过修改其内核源码中附带的8139too.c完成零拷贝的试验，主要想法是：在8139too网卡驱动模块启动时申请一内核缓存，并建立一数据结构对
其进行管理，然后试验性的向该缓存写入多个字符串数据，最后通过proc文件系统将该缓存的地址传给用户进程；用户进程通过读proc文件系统取得缓存地
址并对该缓存进行地址映射，从而可以从其中读取数据。哈哈，为了偷懒，本文只是对零拷贝思想中的地址映射部分进行试验，而没有实现DMA数据传输（太麻烦
了，还得了解硬件），本试验并不是一个IDS产品中抓包模块的一部分，要想真正在IDS中实现零拷贝，除了DMA外，还有一些问题需考虑，详见本文第三节
的分析。以下为实现零拷贝的主要步骤，详细代码见附录。<br />
<br />
<font color="#ff0000"> 步骤一：修改网卡驱动程序</font><br />
<font color="#0000ff"> a．在网卡驱动程序中申请一块缓存</font>：由于在linux2.4.X内核中支持的最大可分配连续缓存大小为2M，所以如果需要存储更大量的网络数据报文，则需要分配多块非连续的缓存，并使用链表、数组或hash表来对这些缓存进行管理。<br />
<br />
#define PAGES_ORDER 9<br />
unsigned long su1_2<br />
su1_2 = __get_free_pages(GFP_KERNEL,PAGES_ORDER);<br />
<br />
<font color="#0000ff"> b. 向缓存中写入数据</font>：真正IDS产品中的零拷贝实现应该是使用DMA数据传输把网卡硬
件接收到的包直接写入该缓存。作为试验，我只是向该缓存中写入几个任意
的字符串，如果不考虑DMA而又想向缓存中写入真正的网络数据包，可以在8139too.c的rtl8139_rx_interrupt()中调用
netif_rx()后插入以下代码：<br />
<br />
//put_pkt2mem_n++; //包个数<br />
//put_mem(skb-&gt;data,pkt_size);<br />
其中put_pkt2mem_n变量和put_mem函数见附录。<br />
<br />
<font color="#0000ff"> c. 把该缓存的物理地址传到用户空间</font>：由于在内核中申请的缓存地址为虚拟地址，而在用户
空间需要得到的是该缓存的物理地址，所以首先要进行虚拟地址到物理地址
的转换，在linux系统中可以使用内核虚拟地址减3G来获得对应的物理地址。把缓存的地址传到用户空间需要在内核与用户空间进行少量数据传输，这可以使
用字符驱动、proc文件系统等方式实现，在这里采用了proc文件系统方式。<br />
<br />
int read_procaddr(char *buf,char **start,off_t offset,int count,int *eof,void *data)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;    sprintf(buf,"%u\n",__pa(su1_2));<br />
&nbsp;&nbsp;&nbsp;&nbsp;    *eof = 1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    return 9;<br />
}<br />
create_proc_read_entry("nf_addr",0,NULL,read_procaddr,NULL);<br />
<br />
<font color="#ff0000"> 步骤二：在用户程序中实现对共享缓存的访问</font><br />
<font color="#0000ff"> a.读取缓存地址</font>：通过直接读取proc文件的方式便可获得。<br />
<br />
char addr[9];<br />
int fd_procaddr;<br />
unsigned long ADDR;<br />
fd_procaddr = open("/proc/nf_addr",O_RDONLY);<br />
read(fd_procaddr,addr,9);<br />
ADDR = atol(addr);<br />
<br />
<font color="#0000ff"> b.把缓存映射到用户进程空间中</font>：在用户进程中打开/dev/mem设备(相当于物理内存），使用mmap把网卡驱动程序申请的缓存映射到自己的进程空间，然后就可以从中读取所需要的网络数据包了。<br />
<br />
char *su1_2;<br />
int fd;<br />
fd=open("/dev/mem",O_RDWR);&nbsp;&nbsp;&nbsp;&nbsp;<br />
su1_2 = mmap(0,PAGES*4*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, ADDR);<br />
<br />
三．分析<br />
&nbsp;&nbsp;&nbsp;&nbsp;
零拷贝中存在的最关键问题是同步问题，一边是处于内核空间的网卡驱动向缓存中写入网络数据包，一边是用户进程直接对缓存中的数据包进行分析（注意，不是拷
贝后再分析），由于两者处于不同的空间，这使得同步问题变得更加复杂。缓存被分成多个小块，每一块存储一个网络数据包并用一数据结构表示，本试验在包数据
结构中使用标志位来标识什么时候可以进行读或写，<font color="#0000ff">当网卡驱动向包数据结构中填入真实的包数据后便标识该包为可读，当用户进程对包数据结构中的数据分析完后 便标识该包为可写，这基本解决了同步问题</font>。然而，由于IDS的分析进程需要直接对缓存中的数据进行入侵分析，而不是将数据拷贝到用户空间后再进行分析，这 使得读操作要慢于写操作，有可能造成网卡驱动无缓存空间可以写，从而造成一定的丢包现象，解决这一问题的关键在于<font color="#0000ff">申请多大的缓存</font>，太小的缓存容易造成丢 包，太大的缓存则管理麻烦并且对系统性能会有比较大的影响。<br />
<br />
四．附录<br />
<font color="#0000ff"> a.&nbsp;&nbsp;&nbsp;&nbsp;    8139too.c中加入的代码</font><br />
<br />
/*add_by_liangjian for zero_copy*/<br />
#include &lt;linux/wrapper.h&gt;<br />
#include &lt;asm/page.h&gt;<br />
#include &lt;linux/slab.h&gt;<br />
#include &lt;linux/proc_fs.h&gt;<br />
#define PAGES_ORDER 9<br />
#define PAGES 512<br />
#define MEM_WIDTH&nbsp;&nbsp;&nbsp;&nbsp;    1500<br />
/*added*/<br />
<br />
/*add_by_liangjian for zero_copy*/<br />
struct MEM_DATA<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;    //int key;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned short width;/*缓冲区宽度*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned short length;/*缓冲区长度*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    //unsigned short wtimes;/*写进程记数,预留，为以后可以多个进程写*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    //unsigned short rtimes;/*读进程记数,预留，为以后可以多个进程读*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned short wi;/*写指针*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned short ri;/*读指针*/<br />
} * mem_data;<br />
struct MEM_PACKET<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned int len;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned char packetp[MEM_WIDTH - 4];/*sizeof(unsigned int) == 4*/<br />
};<br />
unsigned long su1_2;/*缓存地址*/<br />
/*added*/<br />
<br />
/*add_by_liangjian for zero_copy*/<br />
//删除缓存<br />
void del_mem()<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;    int pages = 0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    char *addr;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    addr = (char *)su1_2;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    while (pages &lt;=PAGES -1)<br />
&nbsp;&nbsp;&nbsp;&nbsp;    {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_map_unreserve(virt_to_page(addr));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    addr = addr + PAGE_SIZE;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    pages++;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    }<br />
&nbsp;&nbsp;&nbsp;&nbsp;    free_pages(su1_2,PAGES_ORDER);&nbsp;&nbsp;&nbsp;&nbsp;<br />
}&nbsp;&nbsp;&nbsp;&nbsp;<br />
void init_mem()<br />
/********************************************************<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    初始化缓存<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     输入:&nbsp;&nbsp;     aMode:&nbsp;&nbsp;&nbsp;&nbsp;    缓冲区读写模式:&nbsp;&nbsp;    r,w&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     返回:&nbsp;&nbsp;     00:&nbsp;&nbsp;&nbsp;&nbsp;     失败&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     &gt;0:&nbsp;&nbsp;&nbsp;&nbsp;     缓冲区地址&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
********************************************************/<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;    int i;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    int pages = 0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    char *addr;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    char *buf;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    struct MEM_PACKET * curr_pack;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    su1_2 = __get_free_pages(GFP_KERNEL,PAGES_ORDER);<br />
&nbsp;&nbsp;&nbsp;&nbsp;    printk("[%x]\n",su1_2);<br />
&nbsp;&nbsp;&nbsp;&nbsp;    addr = (char *)su1_2;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    while (pages &lt;= PAGES -1)<br />
&nbsp;&nbsp;&nbsp;&nbsp;    {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_map_reserve(virt_to_page(addr));//需使缓存的页面常驻内存<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    addr = addr + PAGE_SIZE;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    pages++;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    }<br />
&nbsp;&nbsp;&nbsp;&nbsp;    mem_data = (struct MEM_DATA *)su1_2;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    mem_data[0].ri = 1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_data[0].wi = 1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_data[0].length = PAGES*4*1024 / MEM_WIDTH;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_data[0].width = MEM_WIDTH;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    /* initial su1_2 */<br />
&nbsp;&nbsp;&nbsp;&nbsp;    for(i=1;i&lt;=mem_data[0].length;i++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;    {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    buf = (void *)((char *)su1_2 + MEM_WIDTH * i);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    curr_pack = (struct MEM_PACKET *)buf;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    curr_pack-&gt;len = 0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    }&nbsp;&nbsp;&nbsp;&nbsp;<br />
}<br />
int put_mem(char *aBuf,unsigned int pack_size)<br />
/****************************************************************<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     写缓冲区子程序&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     输入参数&nbsp;&nbsp;&nbsp;&nbsp;    :&nbsp;&nbsp;     aMem:&nbsp;&nbsp;     缓冲区地址&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     aBuf:&nbsp;&nbsp;     写数据地址&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     输出参数&nbsp;&nbsp;&nbsp;&nbsp;    :&nbsp;&nbsp;     &lt;=00 :&nbsp;&nbsp;    错误&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     XXXX :&nbsp;&nbsp;    数据项序号&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*****************************************************************/<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;    register int s,i,width,length,mem_i;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    char *buf;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    struct MEM_PACKET * curr_pack;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;    s = 0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    mem_data = (struct MEM_DATA *)su1_2;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    width&nbsp;&nbsp;    = mem_data[0].width;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    length = mem_data[0].length;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    mem_i&nbsp;&nbsp;    = mem_data[0].wi;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    buf = (void *)((char *)su1_2 + width * mem_i);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;    for (i=1;i&lt;length;i++){<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    curr_pack = (struct MEM_PACKET *)buf;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    if&nbsp;&nbsp;    (curr_pack-&gt;len == 0){<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    memcpy(curr_pack-&gt;packetp,aBuf,pack_size);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    curr_pack-&gt;len = pack_size;;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    s = mem_i;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_i++;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    if&nbsp;&nbsp;    (mem_i &gt;= length)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_i = 1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_data[0].wi = mem_i;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    break;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    }<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_i++;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    if&nbsp;&nbsp;    (mem_i &gt;= length){<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_i = 1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    buf = (void *)((char *)su1_2 + width);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    }<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    else buf = (char *)su1_2 + width*mem_i;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    }<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;    if(i &gt;= length)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    s = 0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    return s;<br />
}<br />
// proc文件读函数<br />
int read_procaddr(char *buf,char **start,off_t offset,int count,int *eof,void *data)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;    sprintf(buf,"%u\n",__pa(su1_2));<br />
&nbsp;&nbsp;&nbsp;&nbsp;    *eof = 1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    return 9;<br />
}<br />
/*added*/<br />
<br />
在8139too.c的rtl8139_init_module()函数中加入以下代码：<br />
/*add_by_liangjian for zero_copy*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    put_pkt2mem_n = 0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    init_mem();<br />
&nbsp;&nbsp;&nbsp;&nbsp;    put_mem("data1dfadfaserty",16);<br />
&nbsp;&nbsp;&nbsp;&nbsp;    put_mem("data2zcvbnm",11);<br />
&nbsp;&nbsp;&nbsp;&nbsp;    put_mem("data39876543210poiuyt",21);<br />
&nbsp;&nbsp;&nbsp;&nbsp;    create_proc_read_entry("nf_addr",0,NULL,read_procaddr,NULL);<br />
/*added */&nbsp;&nbsp;&nbsp;&nbsp;<br />
<br />
在8139too.c的rtl8139_cleanup_module()函数中加入以下代码：<br />
/*add_by_liangjian for zero_copy*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    del_mem();<br />
&nbsp;&nbsp;&nbsp;&nbsp;    remove_proc_entry("nf_addr",NULL);<br />
/*added*/&nbsp;&nbsp;&nbsp;&nbsp;<br />
<font color="#0000ff"><br />
b．用户空间读取缓存代码</font><br />
<br />
#include &lt;stdio.h&gt;<br />
#include &lt;unistd.h&gt;<br />
#include &lt;sys/stat.h&gt;<br />
#include &lt;sys/mman.h&gt;<br />
#include &lt;fcntl.h&gt;<br />
#define PAGES 512<br />
#define MEM_WIDTH 1500<br />
struct MEM_DATA<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;    //int key;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned short width;/*缓冲区宽度*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned short length;/*缓冲区长度*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    //unsigned short wtimes;/*写进程记数,预留，为以后可以多个进程写*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    //unsigned short rtimes;/*读进程记数,预留，为以后可以多个进程读*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned short wi;/*写指针*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned short ri;/*读指针*/<br />
} * mem_data;<br />
<br />
struct MEM_PACKET<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned int len;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned char packetp[MEM_WIDTH - 4];/*sizeof(unsigned int) == 4*/<br />
};<br />
<br />
int get_mem(char *aMem,char *aBuf,unsigned int *size)<br />
/****************************************************************<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     读缓冲区子程序&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     输入参数&nbsp;&nbsp;&nbsp;&nbsp;    :&nbsp;&nbsp;     aMem:&nbsp;&nbsp;     缓冲区地址&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     aBuf:&nbsp;&nbsp;     返回数据地址, 其数据区长度应大于*<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     缓冲区宽度&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     输出参数&nbsp;&nbsp;&nbsp;&nbsp;    :&nbsp;&nbsp;     &lt;=00 :&nbsp;&nbsp;    错误&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     XXXX :&nbsp;&nbsp;    数据项序号&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *<br />
*****************************************************************/<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;    register int i,s,width,length,mem_i;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    char&nbsp;&nbsp;&nbsp;&nbsp;     *buf;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    struct MEM_PACKET * curr_pack;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;    s = 0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    mem_data = (void *)aMem;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    width&nbsp;&nbsp;    = mem_data[0].width;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    length = mem_data[0].length;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    mem_i&nbsp;&nbsp;    = mem_data[0].ri;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    buf = (void *)(aMem + width * mem_i);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;    curr_pack = (struct MEM_PACKET *)buf;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    if&nbsp;&nbsp;    (curr_pack-&gt;len != 0){/*第一个字节为0说明该部分为空*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    memcpy(aBuf,curr_pack-&gt;packetp,curr_pack-&gt;len);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *size = curr_pack-&gt;len;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    curr_pack-&gt;len = 0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    s = mem_data[0].ri;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_data[0].ri++;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    if(mem_data[0].ri &gt;= length)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_data[0].ri = 1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    goto ret;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    }<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    for (i=1;i&lt;length;i++){<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_i++;/*继续向后找，最糟糕的情况是把整个缓冲区都找一遍*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    if&nbsp;&nbsp;    (mem_i &gt;= length)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_i = 1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    buf = (void *)(aMem + width*mem_i);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    curr_pack = (struct MEM_PACKET *)buf;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    if&nbsp;&nbsp;    (curr_pack-&gt;len == 0)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    continue;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    memcpy(aBuf,curr_pack-&gt;packetp,curr_pack-&gt;len);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    *size = curr_pack-&gt;len;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    curr_pack-&gt;len = 0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    s = mem_data[0].ri = mem_i;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_data[0].ri++;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    if(mem_data[0].ri &gt;= length)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    mem_data[0].ri = 1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    break;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    }<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;    ret:<br />
&nbsp;&nbsp;&nbsp;&nbsp;    return s;<br />
}<br />
<br />
int main()<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;    char *su1_2;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    char receive[1500];<br />
&nbsp;&nbsp;&nbsp;&nbsp;    int i,j;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    int fd;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    int fd_procaddr;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned int size;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    char addr[9];<br />
&nbsp;&nbsp;&nbsp;&nbsp;    unsigned long ADDR;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    j = 0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    /*open device 'mem' as a media to access the RAM*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    fd=open("/dev/mem",O_RDWR);&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;    fd_procaddr = open("/proc/nf_addr",O_RDONLY);<br />
&nbsp;&nbsp;&nbsp;&nbsp;    read(fd_procaddr,addr,9);<br />
&nbsp;&nbsp;&nbsp;&nbsp;    ADDR = atol(addr);<br />
&nbsp;&nbsp;&nbsp;&nbsp;    close(fd_procaddr);<br />
&nbsp;&nbsp;&nbsp;&nbsp;    printf("%u[%8lx]\n",ADDR,ADDR);<br />
&nbsp;&nbsp;&nbsp;&nbsp;    /*Map the address in kernel to user space, use mmap function*/<br />
&nbsp;&nbsp;&nbsp;&nbsp;    su1_2 = mmap(0,PAGES*4*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, ADDR);<br />
&nbsp;&nbsp;&nbsp;&nbsp;    perror("mmap");<br />
&nbsp;&nbsp;&nbsp;&nbsp;    while(1)<br />
&nbsp;&nbsp;&nbsp;&nbsp;    {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    bzero(receive,1500);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    i = get_mem(su1_2,receive,&amp;size);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    if (i != 0)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    j++;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    printf("%d:%s[size = %d]\n",j,receive,size);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    }&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    else <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    printf("there have no data\n");<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    munmap(su1_2,PAGES*4*1024);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    close(fd);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    break;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    }<br />
&nbsp;&nbsp;&nbsp;&nbsp;    }<br />
&nbsp;&nbsp;&nbsp;&nbsp;    while(1);<br />
}<br />
<br />
五．参考文献<br />
1．CHRISTIAN KURMANN, FELIX RAUCH ,THOMAS M. STRICKER. <br />
Speculative Defragmentation - Leading Gigabit Ethernet to True Zero-Copy Communication<br />
2．ALESSANDRO RUBINI,JONATHAN CORBET.《LINUX DEVICE DRIVERS 2》,O&#8217;Reilly &amp; Associates 2002.<br />
3．胡希明,毛德操.《LINUX 内核源代码情景分析》,浙江大学出版社 2001<br />
<br />
<br />
关
于作者：梁健，华北计算技术研究所在读硕士研究生，研究方向：信息安全。论文开题为《基于系统调用分析的主机异常入侵检测与防御》。对IDS有两年多的研
究经验，熟悉linux内核，熟悉linux c/c++编程、win32 API编程，对网络和操作系统安全感兴趣。<br />
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@<br />
零拷贝技术分为两步： <br />
1、硬件到内核，实现的前提是网卡必须支持DMA，对于不支持DMA的网卡无法实现零拷贝。 <br />
2、内核到用户层，将系统内核中存储数据报的内存区域映射到检测程序的应用程序空间或者在用户空间建立一缓存，并将其映射到内核空间。 <br />
很多相关公司都采用了这种技术Firewall/IDS等，这两种技术已经很成熟了<br />
<br />
摘自：http://hi.baidu.com/msingle/blog/item/0ec4eb239db94e40ad34de18.html<br />
</font>
<img src ="http://www.blogjava.net/shiliqiang/aggbug/290421.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shiliqiang/" target="_blank">石头@</a> 2009-08-09 11:09 <a href="http://www.blogjava.net/shiliqiang/articles/290421.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>硬盘  簇</title><link>http://www.blogjava.net/shiliqiang/articles/290389.html</link><dc:creator>石头@</dc:creator><author>石头@</author><pubDate>Sat, 08 Aug 2009 14:31:00 GMT</pubDate><guid>http://www.blogjava.net/shiliqiang/articles/290389.html</guid><wfw:comment>http://www.blogjava.net/shiliqiang/comments/290389.html</wfw:comment><comments>http://www.blogjava.net/shiliqiang/articles/290389.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shiliqiang/comments/commentRss/290389.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shiliqiang/services/trackbacks/290389.html</trackback:ping><description><![CDATA[<div class="tit"> </div>
<div class="cnt">
<p>文
件系统是操作系统与驱动器之间的接口，当操作系统请求从硬盘里读取一个文件时，会请求相应的文件系统（FAT
16/32/NTFS）打开文件。扇区是磁盘最小的物理存储单元，但由于操作系统无法对数目众多的扇区进行寻址，所以操作系统就将相邻的扇区组合在一起，
形成一个簇，然后再对簇进行管理。每个簇可以包括2、4、8、16、32或64个扇区。显然，簇是操作系统所使用的逻辑概念，而非磁盘的物理特性。 <br />
<br />
为了更好地管理磁盘空间和更高效地从硬盘读取数据，操作系统规定一个簇中只能放置一个文件的内容，因此文件所占用的空间，只能是簇的整数倍；而如果文件实
际大小小于一簇，它也要占一簇的空间。所以，一般情况下文件所占空间要略大于文件的实际大小，只有在少数情况下，即文件的实际大小恰好是簇的整数倍时，文
件的实际大小才会与所占空间完全一致。</p>
<p>文
件系统是操作系统与驱动器之间的接口，当操作系统请求从硬盘里读取一个文件时，会请求相应的文件系统（FAT
16/32/NTFS）打开文件。扇区是磁盘最小的物理存储单元，但由于操作系统无法对数目众多的扇区进行寻址，所以操作系统就将相邻的扇区组合在一起，
形成一个簇，然后再对簇进行管理。每个簇可以包括2、4、8、16、32或64个扇区。显然，簇是操作系统所使用的逻辑概念，而非磁盘的物理特性。 <br />
<br />
为了更好地管理磁盘空间和更高效地从硬盘读取数据，操作系统规定一个簇中只能放置一个文件的内容，因此文件所占用的空间，只能是簇的整数倍；而如果文件实
际大小小于一簇，它也要占一簇的空间。所以，一般情况下文件所占空间要略大于文件的实际大小，只有在少数情况下，即文件的实际大小恰好是簇的整数倍时，文
件的实际大小才会与所占空间完全一致。</p>
<div class="f14">簇是指可分配的用来保存文件的最小磁盘空间，计算机中所有的信息都保存在簇中。簇越小，保存信息的效率就越高。在FAT16文件系统中，每个分区最多有65525个簇，簇大小默认值为32KB；在FAT32文件系统中使用的簇比FAT16小，默认为4KB。 <br />
那么在NTFS文件系统中磁盘簇的大小设为多少才合适呢?下面看看大家的讨论： <br />
<br />
一、在NTFS文件系统中如何设置簇大小 <br />
<br />
默认的情况下，在格式化的时候如果没有指定簇的大小，那么系统会根据分区的大小选择默认的簇值。其实在NTFS文件系统中格式化的时候，可以在
&#8220;Format&#8221;命令后面添加&#8220;/a:UnitSize
&#8221;参数来指定簇的大小，UnitSize表示簇大小的值，NTFS支持512/1024/2048/4096/8192/16K/32K/64K。比如
&#8220;format d:/fs:NTFS /a:2048&#8221;，表示将D盘用NTFS文件系统格式化，簇的值为2048B。 <br />
<br />
二、使用默认的设置 <br />
<br />
对于初学者来说，其实没有必要去手工设置簇的大小，因为一般情况下使用默认的设置就可以了。比如在用NTFS文件系统格式化分区的时候，系统会根据分区的大小自动选择默认的簇大小，比如4KB。 <br />
<br />
三、簇的大小因硬盘分区大小而异 <br />
<br />
在NTFS文件系统中，当分区的大小在2GB以下时，簇的大小应该比相应的FAT32簇小，即小于4KB；当分区的大小在2GB以上时（2GB~2TB），簇的大小应该都为4KB。 <br />
<br />
四、使用压缩功能对簇大小的要求 <br />
<br />
在Windows 2000/XP系统中，为了使用压缩功能来节省磁盘空间，必须遵循两个条件： <br />
<br />
1．磁盘分区必须是NTFS文件系统； <br />
2．分区中簇的大小不得超过4KB（默认簇的大小，即4096字节）。 <br />
<br />
五、簇的大小的影响 <br />
<br />
在NTFS文件系统中，簇的大小会影响到磁盘文件的排列，设置适当的簇大小可以减少磁盘空间丢失和分区上碎片的数量。如果簇设置过大，会影响到磁盘存储效率；反之如果设置过小，虽然会提高利用效率，但是会产生大量磁盘碎片。</div>
<div class="f14">
<div class="f14">硬
盘是计算机中极为重要的存储设备，计算机工作所用到的全部文件系统和数据资料的绝大多数都存储在硬盘中。硬盘是产生计算机软故障最主要的地方，常见的硬盘
软故障有：硬盘重要参数及文件丢失，电脑不能起动；碎片过多，电脑运行速度变慢；硬盘分区后丢失容量等。对付硬盘软故障，只要我们肯动脑并利用一些硬盘维
护工具，发挥一不怕苦、二不怕（硬盘）死的革命精神，外加胆大心细，当然还要掌握硬盘基本常识，这样就可以轻松搞定（说的容易、做起来可不简单 :(
）。因此，我收集了大量的资料整理汇编了&#8220;硬盘软故障完全修复手册&#8221;，希望能在与大家一起学习的过程中掌握硬盘常见故障的排除方法，做到&#8220;自已动手、丰衣
足食&#8221;，凡事不求人的目的。 <br />
大家知道，一个硬盘要能存放文件，必须经过硬盘分区，格式化等操作步骤，因为经过这些步骤之后，在硬盘中就建立起了主分区，引导分区，确定了FAT16或
FAT32文件表。主分区的作用是保存硬盘中各逻辑分区在盘片上起始位置和终止位置及分区的容量大小。引导分区的作用是在固定的位置存放有操作系统文件，
在电脑送电或复位时，由BIOS程序将处于固定位置的系统文件装入内存，再将电脑控制权交给系统文件人而完成引导过程。扩展分区作为一个主分区占用了主分
区表的一个表项。在扩展分区起始位置所指示的扇区（即该分区的第一个扇区）中，包含有第一个逻辑分区表，同样从1BEH字节开始，每个分区表项占用16个
字节。逻辑分区表一般包含两个分区表项，一个指向某逻辑分区，另一个则指向下一个扩展分区。下一个扩展分区的首扇区又包含了一个逻辑分区表， <br />
这样以此类推，扩展分区中就可以包含多个逻辑分区。下面我们就来学习一下硬盘数据的基本结构。 <br />
★ 硬盘的数据结构 ★ <br />
① MBR（Main Boot Record 主引导记录区） <br />
MBR位于整个硬盘的0磁道0柱面1扇区，包括硬盘引导程序和分区表。在总共512字节的硬盘主引导扇区中，MBR只占用了其中的446个字节，其最后两
个字节&#8220;55 AA&#8221;是分区的结束标志。另外的64个字节交给了DPT（Disk Partition Table
硬盘分区表），从1BEH字节开始，共占用64个字节，包含四个分区表项。每个分区表项的长度为16个字节，它包含一个分区的引导标志、系统标志、起始和
结尾的柱面号、扇区号、磁头号以及本分区前面的扇区数和本分区所占用的扇区数。其中&#8220;引导标志&#8221;表明此分区是否可引导，即是否活动分区。当引导标志为
&#8220;80&#8221;时，此分区为活动分区；&#8220;系统标志&#8221;决定了该分区的类型，如&#8220;06&#8221;为FAT16分区，&#8220;0B&#8221;为FAT32分区，&#8220;07&#8221;为NTFS分
区，&#8220;63&#8221;为UNIX分区，等；起始和结尾的柱面号、扇区号、磁头号指明了该分区的起始和终止位置。 <br />
我们假设一个硬盘分区表从1BEH字节开始的16个字节为 80 01 01 00 06 0D 68 6D 28 00 00 00 78 20 03 00 <br />
硬盘分区表项的16个字节分配如下： <br />
第1字节：是一个分区的激活标志，表示系统可引导。如是0则表示非活动分区。 <br />
第2字节：该分区起始磁头（HEAD）号 <br />
第3字节：该分区起始扇区（Sector）号 <br />
第4字节：该分区起始的柱面（Cylinder）号 <br />
第5字节：该分区系统类型标志 <br />
第6—8字节：该分区终止磁头（HEAD）号、分区结束的扇区号、分区结束的柱面号 <br />
第9-12字节：该分区首扇区的相对扇区号 <br />
第13-16字节：该分区占用的扇区总数 <br />
以上参数我们可以用NU 8.0中DISKEDIT工具软件可轻松获取，其功能非常强大，但应用不当会有很大错误，请各位注意使用方法。操作步骤如下： <br />
以一台硬盘为270 MB，分为C盘（100 MB）和D盘（170 MB）的机子（老掉牙了 ^_^）为例，在纯DOS下启动DISKEDIT &#8594;
在对象菜单（Object）上选中驱动器（Drive）和物理磁盘选项后确定 &#8594; 在对象菜单（Object）上选中分区表（Partition
Table） &#8594; 在显示菜单（View）中选择十六进制（Hex） <br />
以下数据为主分区信息： <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 />
② DBR（Dos Boot Record 操作系统引导记录区） <br />
它通常位于硬盘的0磁道1柱面1扇区，是操作系统可直接访问的第一个扇区，它包括一个引导程序和一个被称为BPB（BIOS Parameter
Block）的本分区参数记录表。引导程序的主要任务是当MBR将系统控制权交给它时，判断本分区跟目录前两个文件是不是操作系统的引导文件（以DOS为
例，即是Io.sys和Msodos.sys）。如果确定存在，就把它们读入内存，并把控制权交给该文件。BPB参数块记录着本分区的起始扇区、结束扇
区、文件存储格式、硬盘介质描述符、根目录大小、FAT个数、分配单元的大小等重要参数。DBR是由高级格式化程序（即Format等程序）所产生的。
<br />
③ FAT（File Allocation Table 文件分配表） <br />
FAT是DOS、Windows 9X系统的文件寻址格式，位于DBR之后。 <br />
在解释文件分配表的概念的时候，我们有必要谈谈簇（Cluster）的概念。文件占用磁盘空间，基本单位不是字节而是簇。一般情况下，软盘每簇是1个扇区，硬盘每簇的扇区数与硬盘的总容量大小有关，可能是4、8、16、32、64&#8230;&#8230; <br />
同一个文件的数据并不一定完整地存放在磁盘的一个连续的区域内，而往往会分成若干段，像一条链子一样存放。这种存储方式称为文件的链式存储。由于硬盘上保
存着段与段之间的连接信息（即FAT），操作系统在读取文件时，总是能够准确地找到各段的位置并正确读出。 <br />
为了实现文件的链式存储，硬盘上必须准确地记录哪些簇已经被文件占用，还必须为每个已经占用的簇指明存储后继内容的下一个簇的簇号。对一个文件的最后一
簇，则要指明本簇无后继簇。这些都是由FAT表来保存的，表中有很多表项，每项记录一个簇的信息。由于FAT对于文件管理的重要性，所以为了安全起
见，FAT有一个备份，即在原FAT的后面再建一个同样的FAT。初形成的FAT中所有项都标明为&#8220;未占用&#8221;，但如果磁盘有局部损坏，那么格式化程序会检
测出损坏的簇，在相应的项中标为&#8220;坏簇&#8221;，以后存文件时就不会再使用这个簇了。FAT的项数与硬盘上的总簇数相当，每一项占用的字节数也要与总簇数相适
应，因为其中需要存放簇号。FAT的格式有多种，最为常见的是FAT16和FAT32。 <br />
④ DIR （Directory 根目录区） <br />
DIR位于第二个FAT表之后，记录着根目录下每个文件（目录）的起始单元，文件的属性等。定位文件位置时，操作系统根据DIR中的起始单元，结合FAT表就可以知道文件在硬盘中的具体位置和大小了。 <br />
⑤ DATA（数据区） <br />
数据区是真正意义上的数据存储的地方，位于DIR区之后，占据硬盘的大部分空间。当将数据复制到硬盘时，数据就存放在DATA区。</div>
</div>
</div>
<img src ="http://www.blogjava.net/shiliqiang/aggbug/290389.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shiliqiang/" target="_blank">石头@</a> 2009-08-08 22:31 <a href="http://www.blogjava.net/shiliqiang/articles/290389.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个正则表达式工具类</title><link>http://www.blogjava.net/shiliqiang/articles/290312.html</link><dc:creator>石头@</dc:creator><author>石头@</author><pubDate>Sat, 08 Aug 2009 01:01:00 GMT</pubDate><guid>http://www.blogjava.net/shiliqiang/articles/290312.html</guid><wfw:comment>http://www.blogjava.net/shiliqiang/comments/290312.html</wfw:comment><comments>http://www.blogjava.net/shiliqiang/articles/290312.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shiliqiang/comments/commentRss/290312.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shiliqiang/services/trackbacks/290312.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 一个java正规表达式工具类类中用到了 jakarta-oro-2.0.jar 包，请大家自己在 apache网站下下载在这是junit测试单元类我就不提交了，在main()方法中有几个小测试，有兴趣自己玩吧.这个工具类目前主要有25种正规表达式(有些不常用，但那时才仔细深入的研究了一下正规，写上瘾了，就当时能想到的都写了):匹配图象; 2 匹配email地...&nbsp;&nbsp;<a href='http://www.blogjava.net/shiliqiang/articles/290312.html'>阅读全文</a><img src ="http://www.blogjava.net/shiliqiang/aggbug/290312.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shiliqiang/" target="_blank">石头@</a> 2009-08-08 09:01 <a href="http://www.blogjava.net/shiliqiang/articles/290312.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个合格的程序员应具备的。。。</title><link>http://www.blogjava.net/shiliqiang/articles/289318.html</link><dc:creator>石头@</dc:creator><author>石头@</author><pubDate>Fri, 31 Jul 2009 12:45:00 GMT</pubDate><guid>http://www.blogjava.net/shiliqiang/articles/289318.html</guid><wfw:comment>http://www.blogjava.net/shiliqiang/comments/289318.html</wfw:comment><comments>http://www.blogjava.net/shiliqiang/articles/289318.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shiliqiang/comments/commentRss/289318.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shiliqiang/services/trackbacks/289318.html</trackback:ping><description><![CDATA[每个程序员都应牢记的7种坏味道，11种原则，23种模式
<br />
<br />
(一)7种设计坏味道
<br />
1.僵化性： 很难对系统进行改动，因为每个改动都会迫使许多对系统其他部分的其它改动。
<br />
2.脆弱性： 对系统的改动会导致系统中和改动的地方在概念上无关的许多地方出现问题。
<br />
3.牢固性： 很难解开系统的纠结，使之成为一些可在其他系统中重用的组件。
<br />
4.粘滞性： 做正确的事情比做错误的事情要困难。
<br />
5.复杂性(不必要的)： 设计中包含有不具任何直接好处的基础结构。
<br />
6.重复性(不必要的)： 设计中包含有重复的结构，而该重复的结构本可以使用单一的抽象进行统一。
<br />
7.晦涩性： 很难阅读、理解。没有很好地表现出意图。
<br />
<br />
(二)11种原则 - Principle
<br />
----类原则
<br />
1.单一职责原则 - Single Responsibility Principle(SRP)
<br />
就一个类而言，应该仅有一个引起它变化的原因。
<br />
(职责即为&#8220;变化的原因&#8221;。)
<br />
2.开放-封闭原则 - Open Close Principle(OCP)
<br />
软件实体（类、模块、函数等）应该是可以扩展的，但是不可修改。
<br />
(对于扩展是开放的,对于更改是封闭的.
<br />
关键是抽象.将一个功能的通用部分和实现细节部分清晰的分离开来.
<br />
开发人员应该仅仅对程序中呈现出频繁变化的那些部分作出抽象.
<br />
拒绝不成熟的抽象和抽象本身一样重要. )
<br />
3.里氏替换原则 - Liskov Substitution Principle(LSP)
<br />
子类型(subclass)必须能够替换掉它们的基类型(superclass)。
<br />
4.依赖倒置原则(IoCP) 或 依赖注入原则 - Dependence Inversion Principle(DIP)
<br />
抽象不应该依赖于细节。细节应该依赖于抽象。
<br />
(Hollywood原则: "Don't call us, we'll call you".
<br />
程序中所有的依赖关系都应该终止于抽象类和接口。
<br />
针对接口而非实现编程。
<br />
任何变量都不应该持有一个指向具体类的指针或引用。
<br />
任何类都不应该从具体类派生。
<br />
任何方法都不应该覆写他的任何基类中的已经实现了的方法。)
<br />
5.接口隔离原则(ISP)
<br />
不应该强迫客户依赖于它们不用的方法。
<br />
接口属于客户，不属于它所在的类层次结构。
<br />
(多个面向特定用户的接口胜于一个通用接口。)
<br />
----包内聚原则
<br />
6.重用发布等价原则(REP)
<br />
重用的粒度就是发布的粒度。
<br />
7.共同封闭原则(CCP)
<br />
包中的所有类对于同一类性质的变化应该是共同封闭的。
<br />
一个变化若对一个包产生影响，
<br />
则将对该包中的所有类产生影响，
<br />
而对于其他的包不造成任何影响。
<br />
8.共同重用原则(CRP)
<br />
一个包中的所有类应该是共同重用的。
<br />
如果重用了包中的一个类，
<br />
那么就要重用包中的所有类。
<br />
(相互之间没有紧密联系的类不应该在同一个包中。)
<br />
----包耦合原则
<br />
9.无环依赖原则(ADP)
<br />
在包的依赖关系图中不允许存在环。
<br />
10.稳定依赖原则(SDP)
<br />
朝着稳定的方向进行依赖。
<br />
应该把封装系统高层设计的软件（比如抽象类）放进稳定的包中，
<br />
不稳定的包中应该只包含那些很可能会改变的软件（比如具体类）。
<br />
11.稳定抽象原则(SAP)
<br />
包的抽象程度应该和其稳定程度一致。
<br />
(一个稳定的包应该也是抽象的，一个不稳定的包应该是抽象的. )
<br />
----其它扩展原则----
<br />
12.BBP(Black Box Principle)黑盒原则
<br />
多用类的聚合，少用类的继承。
<br />
13.DAP(Default Abstraction Principle)缺省抽象原则
<br />
在接口和实现接口的类之间引入一个抽象类,这个类实现了接口的大部分操作.
<br />
14.IDP(Interface Design Principle)接口设计原则
<br />
规划一个接口而不是实现一个接口。
<br />
15.DCSP(Don't Concrete Supperclass Principle)不要构造具体的超类原则
<br />
避免维护具体的超类。
<br />
16.迪米特法则
<br />
一个类只依赖其触手可得的类。
<br />
<br />
(三)23种设计模式 - Pattern.
<br />
创建型
<br />
Abstract Factory（抽象工厂模式） -&gt; (简单工厂模式)
<br />
Factory Method（工厂模式）
<br />
Builder（生成器模式）
<br />
Singleton（单件模式） -&gt; (多例模式)
<br />
Prototype（原型模式）
<br />
结构型
<br />
Adapter（适配器模式）
<br />
Bridge（桥接模式）
<br />
Composite（组合模式）
<br />
Decorator（装饰模式）
<br />
Facade（外观模式，门面模式）
<br />
Flyweight（享元模式） -&gt; (不变模式)
<br />
Proxy（代理模式）
<br />
行为型
<br />
Chain of Responsibility（职责链模式）
<br />
Command（命令模式）
<br />
Interpreter（解释器模式）
<br />
Iteartor（迭代器模式）
<br />
Mediator（中介者模式）
<br />
Memento（备忘录模式）
<br />
Observer（观察者模式）
<br />
State（状态模式）
<br />
Strategy（策略模式）
<br />
TemplateMethod（模板方法模式）
<br />
Visitor（访问者模式）<br />
<br />
<br />
<br />
出自：http://www.javaeye.com/topic/41096<br />
<img src ="http://www.blogjava.net/shiliqiang/aggbug/289318.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shiliqiang/" target="_blank">石头@</a> 2009-07-31 20:45 <a href="http://www.blogjava.net/shiliqiang/articles/289318.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>网络服务器的性能分析</title><link>http://www.blogjava.net/shiliqiang/articles/288646.html</link><dc:creator>石头@</dc:creator><author>石头@</author><pubDate>Mon, 27 Jul 2009 14:13:00 GMT</pubDate><guid>http://www.blogjava.net/shiliqiang/articles/288646.html</guid><wfw:comment>http://www.blogjava.net/shiliqiang/comments/288646.html</wfw:comment><comments>http://www.blogjava.net/shiliqiang/articles/288646.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shiliqiang/comments/commentRss/288646.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shiliqiang/services/trackbacks/288646.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 这篇文章分析网络服务的系统响应速度，阅读这篇文章需要一定的操作系统基础和网络编程基础，建议阅读《操作系统概念》、《Windows网络编程》、《UNIX高级网络编程》。&nbsp;网络服务的系统响应速度就是提交一个处理请求给网络服务系统开始计时，直到网络服务系统返回处理结果为止的时间间隔。系统响应速度越快表明服务器处理效率越高，用户满意度也越高。&nbsp;网络服务结构先看一个典...&nbsp;&nbsp;<a href='http://www.blogjava.net/shiliqiang/articles/288646.html'>阅读全文</a><img src ="http://www.blogjava.net/shiliqiang/aggbug/288646.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shiliqiang/" target="_blank">石头@</a> 2009-07-27 22:13 <a href="http://www.blogjava.net/shiliqiang/articles/288646.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>程序性能分析</title><link>http://www.blogjava.net/shiliqiang/articles/288641.html</link><dc:creator>石头@</dc:creator><author>石头@</author><pubDate>Mon, 27 Jul 2009 13:37:00 GMT</pubDate><guid>http://www.blogjava.net/shiliqiang/articles/288641.html</guid><wfw:comment>http://www.blogjava.net/shiliqiang/comments/288641.html</wfw:comment><comments>http://www.blogjava.net/shiliqiang/articles/288641.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shiliqiang/comments/commentRss/288641.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shiliqiang/services/trackbacks/288641.html</trackback:ping><description><![CDATA[<div><font size="2">这篇文章主要是想谈谈在以<font face="Times New Roman">CPU</font>为中心的计算体系结构中影响程序性能的主要因素和性能的分析方法以及多线程对程序性能的影响，读这篇文章首先要具备一定的体系结构和操作系统基础，特别是进程调度，建议看《<font face="Times New Roman">Operation System Concept</font>》（中文《操作系统概论》）。</font></div>
<div>&nbsp;</div>
<div><font size="2">先定义一下程序的性能，就是在单位时间内能执行的任务数或者执行某个任务需要的时间。显然，在更短的时间内执行更多的任务性能就越高。</font></div>
<div>&nbsp;</div>
<h1><font size="4"><font face="Times New Roman">CPU</font>和<font face="Times New Roman">IO</font>操作</font></h1>
<div>&nbsp;</div>
<div><font size="2">言归正传，先看一个经典的入门的<font face="Times New Roman">C</font>程序<font face="Times New Roman">Hello World!</font></font></div>
<div align="left"><font size="2">
<table style="border-collapse: collapse;" bgcolor="#f1f1f1" border="1" bordercolor="#999999" cellpadding="0" cellspacing="0" width="95%">
    <tbody>
        <tr>
            <td>
            <p style="margin: 5px; line-height: 150%;"><code><span style="color: #000000;"><font face="新宋体"><span style="color: #0000ff;">int</span> main<span style="color: #0000cc;">(</span><span style="color: #0000ff;">int</span> argc<span style="color: #0000cc;">,</span> <span style="color: #0000ff;">char</span> <span style="color: #0000cc;">*</span> args<span style="color: #0000cc;">[</span><span style="color: #0000cc;">]</span><span style="color: #0000cc;">)</span><br />
            <span style="color: #0000cc;">{</span><br />
            &nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000ff;">int</span> m <span style="color: #0000cc;">=</span> 0<span style="color: #0000cc;">;</span><br />
            &nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000ff;">for</span> <span style="color: #0000cc;">(</span><span style="color: #0000ff;">int</span> i <span style="color: #0000cc;">=</span> 0<span style="color: #0000cc;">;</span>i <span style="color: #0000cc;">&lt;</span> 10<span style="color: #0000cc;">;</span>i <span style="color: #0000cc;">+</span><span style="color: #0000cc;">+</span><span style="color: #0000cc;">)</span><br />
            &nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000cc;">{</span><br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m <span style="color: #0000cc;">=</span> m<span style="color: #0000cc;">+</span>i<span style="color: #0000cc;">;</span><br />
            &nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000cc;">}</span><br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #ff0000;">printf</span><span style="color: #0000cc;">(</span><span style="color: #ff00ff;">"Hello World! 1+2+&#8230;+10=%d\n"</span><span style="color: #0000cc;">,</span> m<span style="color: #0000cc;">)</span><span style="color: #0000cc;">;</span><br />
            <br />
            &nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000ff;">return</span> 0<span style="color: #0000cc;">;</span><br />
            <span style="color: #0000cc;">}</span></font></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
</font></div>
<div align="left"><font size="2">这个不算原始的经典的<font face="Times New Roman">Hello World</font>，比那个<font face="Times New Roman">Hello World</font>稍微复杂了点，加了一个循环，用来计算<font face="Times New Roman">1+2+3&#8230;+10</font>的值。</font></div>
<div>&nbsp;</div>
<div><font size="2">如果有了操作系统进程调度的基础，可以知道这个程序分成两段执行，第一段是计算<font face="Times New Roman">1+2+&#8230;+10</font>的值，主要在<font face="Times New Roman">CPU</font>（中央处理单元）中进行，<font face="Times New Roman">C</font>代码：</font></div>
<div>
<table style="border-collapse: collapse;" bgcolor="#f1f1f1" border="1" bordercolor="#999999" cellpadding="0" cellspacing="0" width="95%">
    <tbody>
        <tr>
            <td>
            <p style="margin: 5px; line-height: 150%;"><code><span style="color: #000000;"><font face="新宋体"><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp; int</span> m <span style="color: #0000cc;">=</span> 0<span style="color: #0000cc;">;</span><br />
            &nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000ff;">for</span> <span style="color: #0000cc;">(</span><span style="color: #0000ff;">int</span> i <span style="color: #0000cc;">=</span> 0<span style="color: #0000cc;">;</span>i <span style="color: #0000cc;">&lt;</span> 10<span style="color: #0000cc;">;</span>i <span style="color: #0000cc;">+</span><span style="color: #0000cc;">+</span><span style="color: #0000cc;">)</span><br />
            &nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000cc;">{</span><br />
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m <span style="color: #0000cc;">=</span> m<span style="color: #0000cc;">+</span>i<span style="color: #0000cc;">;</span><br />
            &nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000cc;">}</span></font></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
</div>
<div align="left">
</div>
<div><font size="2">第二段是将计算结果输出到控制台的这段，将一串文本通过显卡驱动，传送到显示器上显示，主要在显卡上进行，<font face="Times New Roman">C</font>代码：</font></div>
<div align="left"><font size="2">
<table style="border-collapse: collapse;" bgcolor="#f1f1f1" border="1" bordercolor="#999999" cellpadding="0" cellspacing="0" width="95%">
    <tbody>
        <tr>
            <td>
            <p style="margin: 5px; line-height: 150%;"><code><span style="color: #000000;"><font face="新宋体"><span style="color: #ff0000;">printf</span><span style="color: #0000cc;">(</span><span style="color: #ff00ff;">"Hello World! 1+2+&#8230;+10=%d\n"</span><span style="color: #0000cc;">,</span> m<span style="color: #0000cc;">)</span><span style="color: #0000cc;">;</span></font></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
</font></div>
<div><font size="2">整个程序顺序执行，所以<font face="Times New Roman">CPU</font>先计算完成得到<font face="Times New Roman">1+2+&#8230;+10</font>的值后，将这个值转换成一串字符串，然后将字符串发送给显示器，等待显示器显示完成后，整个程序结束，如果将<font face="Times New Roman">CPU</font>执行表示为蓝色，将显示器执行表示为红色，那么程序执行流程如下：</font></div>
<div>
<div align="center"><img src="http://blogimg.chinaunix.net/blog/upfile2/071108134753.jpg" onload="javascript:if(this.width  alt=" />500)this.width=500;" border="0"&gt;</div>
</div>
<div align="center"><font size="2">图<font face="Times New Roman">1</font></font></div>
<div><font size="2">假设<font face="Times New Roman">CPU</font>中计算<font face="Times New Roman">1+2+&#8230;+10</font>和将这个值变成字符串花费了<font face="Times New Roman">11ns</font>（纳秒），而显卡将字符串显示到显示器上花费了<font face="Times New Roman">7ns</font>，那么整个程序运行花费了<font face="Times New Roman">18ns</font>。</font></div>
<div>&nbsp;</div>
<div><font size="2"><font face="Times New Roman">Hello World</font>是最简单的程序，也是所有其他程序的基础，在以<font face="Times New Roman">CPU</font>为中心的计算机结构中，内存负责程序的存储，<font face="Times New Roman">CPU</font>负责程序的运算和流程控制，其他元件被看成跟上面<font face="Times New Roman">Hello World</font>中显卡类似的外围设备，也被称作<font face="Times New Roman">IO</font>设备，所以任何程序都可以看作是一系列<font face="Times New Roman">CPU</font>操作和一系列<font face="Times New Roman">IO</font>操作的符合体，如下图所示：</font></div>
<div><font size="2">
<div align="center"><img src="http://blogimg.chinaunix.net/blog/upfile2/071108134818.jpg" onload="javascript:if(this.width  alt=" />500)this.width=500;" border="0" width="500"&gt;</div>
</font></div>
<div align="center"><font size="2">图<font face="Times New Roman">2</font></font></div>
<div><font size="2"><strong>所以影响程序性能的主要因素有两个方面：一是<font face="Times New Roman">CPU</font></strong><strong>操作的快慢，二是<font face="Times New Roman">IO</font></strong><strong>操作的快慢。</strong></font></div>
<div>&nbsp;</div>
<div><font size="2"><strong>所以程序性能分析的主要方法就是正确区分哪些是<font face="Times New Roman">CPU</font></strong><strong>操作，哪些是<font face="Times New Roman">IO</font></strong><strong>操作。</strong></font></div>
<div>&nbsp;</div>
<div><font size="2"><font face="Times New Roman">CPU</font>操作通常有这些：</font></div>
<div><font size="2">赋值和计算，如：<font face="Times New Roman">m = i*j;</font></font></div>
<div><font size="2">流程控制，如：<font face="Times New Roman">while(true) { i ++;}</font></font></div>
<div>&nbsp;</div>
<div><font size="2"><font face="Times New Roman">IO</font>操作通常有这些：</font></div>
<div><font size="2">磁盘文件操作。</font></div>
<div><font size="2">网络操作。</font></div>
<div><font size="2">键盘和鼠标操作。</font></div>
<div><font size="2">显卡操作，如在屏幕上绘图，显示文本等。</font></div>
<div><font size="2"><font face="Times New Roman">USB</font>操作。</font></div>
<div><font size="2">串口操作。</font></div>
<div><font size="2">红外线操作。</font></div>
<div><font size="2">磁带机操作。</font></div>
<div><font size="2">通常除<font face="Times New Roman">CPU</font>和内存外的其他设备都可以看成<font face="Times New Roman">IO</font>操作，内存之所以不看作<font face="Times New Roman">IO</font>设备，是因为内存访问相对<font face="Times New Roman">IO</font>而言，通常要快几个数量级，所以像<font face="Times New Roman">char * buff = new char[100];</font>这样的操作通常也看作<font face="Times New Roman">CPU</font>操作。</font></div>
<div>&nbsp;</div>
<div><font size="2"><strong>通过分析划分出程序的<font face="Times New Roman">CPU</font></strong><strong>操作和<font face="Times New Roman">IO</font></strong><strong>操作程序段后，可以有针对性的进行优化。</strong></font></div>
<div>&nbsp;</div>
<div><font size="2">对于<font face="Times New Roman">CPU</font>操作，常用的提升性能的方法是优化计算和流程控制代码，如相乘计算<font face="Times New Roman"> m = i * 8</font>，可以使用<font face="Times New Roman"> m = i &lt;&lt; 3</font>，因为位操作比乘法操作速度快，通常在某种语言中都会讲到程序的优化，就属于优化<font face="Times New Roman">CPU</font>操作速度。</font></div>
<div><font size="2">对于<font face="Times New Roman">IO</font>操作，如果<font face="Times New Roman">IO</font>操作过于频繁而成为系统瓶颈，可以清除一些不必要的<font face="Times New Roman">IO</font>操作，也可以更换速度更快的<font face="Times New Roman">IO</font>设备来提高速度，如把硬盘从<font face="Times New Roman">5400</font>转提升到<font face="Times New Roman">7200</font>转。</font></div>
<div>&nbsp;</div>
<h1><font size="4">多线程</font></h1>
<div>&nbsp;</div>
<div><font size="2">下面看看多线程对程序性能的影响，什么时候该使用多线程，什么时候使用多线程达不到预期的效果。</font></div>
<div><font size="2">多线程是程序里面有像上面那样的多个执行流程，这些执行流程独立或者联合起来完成某些任务。</font></div>
<div><font size="2">先看看计算机只有一个<font face="Times New Roman">CPU</font>，一个<font face="Times New Roman">IO</font>设备，程序有两个线程，两个线程执行同样的代码，可以画出执行流程：</font></div>
<div><font size="2">
<div align="center"><img src="http://blogimg.chinaunix.net/blog/upfile2/071108134842.jpg" onload="javascript:if(this.width  alt=" />500)this.width=500;" border="0" width="500"&gt;</div>
</font></div>
<div align="center"><font size="2">图<font face="Times New Roman">3</font></font></div>
<div><font size="2">线程<font face="Times New Roman">1</font>按正常的执行流程执行，线程<font face="Times New Roman">2</font>虽然跟线程<font face="Times New Roman">1</font>执行同样的代码，却出现很多不连续的片段，比如<font face="Times New Roman">2.2</font>&#224;<font face="Times New Roman">2.3</font>和<font face="Times New Roman">2.4</font>&#224;<font face="Times New Roman">2.5</font>，这是因为只有一个<font face="Times New Roman">CPU</font>，所以<font face="Times New Roman">CPU</font>在进行线程<font face="Times New Roman">1</font>的<font face="Times New Roman">CPU</font>操作时，不能同时进行线程<font face="Times New Roman">2</font>的<font face="Times New Roman">CPU</font>操作，也就是<font face="Times New Roman">2.4</font>和<font face="Times New Roman">2.5</font>本来是跟线程<font face="Times New Roman">1</font>的<font face="Times New Roman">1.3</font>代码一样，但是却被<font face="Times New Roman">CPU</font>分两次执行，因为<font face="Times New Roman">CPU</font>正在执行<font face="Times New Roman">1.7</font>。<font face="Times New Roman">2.2</font>和<font face="Times New Roman">2.3</font>也是同样的道理，因为<font face="Times New Roman">IO</font>设备要执行<font face="Times New Roman">1.4</font>的代码，所以<font face="Times New Roman">2.2</font>和<font face="Times New Roman">2.3</font>被打断。但是两个线程的<font face="Times New Roman">CPU</font>操作和<font face="Times New Roman">IO</font>操作在时间上可以重叠，因为他们是不同的设备。</font></div>
<div><font size="2"><strong>也就是在时间上，<font face="Times New Roman">CPU</font></strong><strong>和<font face="Times New Roman">IO</font></strong><strong>设备只能同时做一件事情，<font face="Times New Roman">CPU</font></strong><strong>和<font face="Times New Roman">IO</font></strong><strong>设备可以各自做自己的事情。</strong></font></div>
<div><font size="2">考察一种极端的情况，假设某个程序没有<font face="Times New Roman">IO</font>操作，只有<font face="Times New Roman">CPU</font>操作，那么流程图变成：</font></div>
<div><font size="2">
<div align="center"><img src="http://blogimg.chinaunix.net/blog/upfile2/071108134859.jpg" onload="javascript:if(this.width  alt=" />500)this.width=500;" border="0" width="500"&gt;</div>
</font></div>
<div align="center"><font size="2">图<font face="Times New Roman">4</font></font></div>
<div><font size="2">线程<font face="Times New Roman">1</font>将占用所有的<font face="Times New Roman">CPU</font>时间，线程<font face="Times New Roman">2</font>将一直等待直到线程<font face="Times New Roman">1</font>完成，因为线程<font face="Times New Roman">1</font>完成任务后，依然可以再次执行任务，所以这时使用线程<font face="Times New Roman">1</font>完成任务和使用线程<font face="Times New Roman">2</font>完成任务没有区别，也就是线程<font face="Times New Roman">2</font>的存在并不会让程序多完成一些任务，所以线程<font face="Times New Roman">2</font>的存在，并不能提升程序性能。</font></div>
<div><font size="2"><strong>所以，如果一个程序只有<font face="Times New Roman">CPU</font></strong><strong>操作，那么多线程并不能提升程序性能。</strong></font></div>
<div><font size="2"><strong>同理，如果一个程序只有<font face="Times New Roman">IO</font></strong><strong>操作，那么多线程并不能提升程序性能。</strong></font></div>
<div>&nbsp;</div>
<div><font size="2">但是多线程在现实中确实有提高程序性能的时候，那是因为实际的程序像图<font face="Times New Roman">3</font>那样，有<font face="Times New Roman">CPU</font>操作和<font face="Times New Roman">IO</font>操作组成，<font face="Times New Roman">CPU</font>操作和<font face="Times New Roman">IO</font>操作在时间上可以重叠，所以，同一时间内，程序可以做更多的事情。</font></div>
<div><font size="2">如果一个线程中<font face="Times New Roman">CPU</font>操作时间为<font face="Times New Roman">M</font>，<font face="Times New Roman">IO</font>操作时间为<font face="Times New Roman">N</font>，那么在单位时间内，平均有<font face="Times New Roman">M/(M+N)</font>在处理<font face="Times New Roman">CPU</font>操作，有<font face="Times New Roman">N/(M+N)</font>的时间<font face="Times New Roman">CPU</font>空闲，如果要让<font face="Times New Roman">CPU</font>充分利用，那么可以增加<font face="Times New Roman">(N/(M+N))/(M/(M+N))=N/M</font>个线程来填补<font face="Times New Roman">CPU</font>操作的空白，这样<font face="Times New Roman">CPU</font>能<font face="Times New Roman">100%</font>被利用，如果线程再增加，<font face="Times New Roman">CPU</font>没有空闲，几乎不会增加程序性能。</font></div>
<div><font size="2"><strong>所以，让<font face="Times New Roman">CPU 100%</font></strong><strong>利用的线程最大数为<font face="Times New Roman">1+N/M</font></strong><strong>。</strong></font></div>
<div><font size="2"><strong>同你，让<font face="Times New Roman">IO</font></strong><strong>设备<font face="Times New Roman">100%</font></strong><strong>利用的线程最大数为<font face="Times New Roman">1+M/N</font></strong><strong>。</strong></font></div>
<div><font size="2">这两个公式只是一个度量式，不是一个计算式，因为随着线程数的增加，<font face="Times New Roman">CPU</font>操作时间和<font face="Times New Roman">IO</font>操作时间将会随着变化，<font face="Times New Roman">M</font>和<font face="Times New Roman">N</font>不再固定。</font></div>
<div>&nbsp;</div>
<div><font size="2">看两种常用的程序，服务器程序和用户交互程序。</font></div>
<div><font size="2">服务器程序通常提供某种网络服务，如<font face="Times New Roman">WEB</font>服务器，这种程序要求能最大化的利用<font face="Times New Roman">CPU</font>和<font face="Times New Roman">IO</font>，在单位时间内处理尽可能多的任务，所以应该使用尽可能时<font face="Times New Roman">CPU</font>和<font face="Times New Roman">IO</font>都满符合工作，多线程数可以取<font face="Times New Roman">1+N/M</font>和<font face="Times New Roman">1+M/N</font>中较小的值，如果观察服务器的<font face="Times New Roman">CPU</font>和<font face="Times New Roman">IO</font>使用率，会发现他们常常接近<font face="Times New Roman">90%</font>。</font></div>
<div><font size="2">用户交互程序通常根据用户的某些输入进行相应的操作，操作完成再次等待用户输入，如<font face="Times New Roman">Microsoft Word</font>，要求对用户的输入能及时反应，所以操作线程的<font face="Times New Roman">CPU</font>操作和<font face="Times New Roman">IO</font>操作应该有一定的空闲，使得用户输入线程能随时获取<font face="Times New Roman">CPU</font>来响应用户的输入，使用<font face="Times New Roman">Microsoft Windows</font>时，打开任务管理器，可以发现<font face="Times New Roman">CPU</font>使用率常常很低，如<font face="Times New Roman">1%</font>～<font face="Times New Roman">20%</font>。</font></div>
<div>&nbsp;</div>
<h1><font size="4"><font face="Times New Roman">IO</font>复用</font></h1>
<div>&nbsp;</div>
<div><font size="2">从上面的分析可以看出，多线程提升程序性能，主要得益于让<font face="Times New Roman">CPU</font>和<font face="Times New Roman">IO</font>设备能并行操作，另一种让<font face="Times New Roman">CPU</font>和<font face="Times New Roman">IO</font>设备并行操作的方法是<font face="Times New Roman">IO</font>复用，基本的思想是需要进行<font face="Times New Roman">IO</font>操作时，只是发送一个<font face="Times New Roman">IO</font>操作请求给<font face="Times New Roman">IO</font>设备而不必等待<font face="Times New Roman">IO</font>完成，<font face="Times New Roman">CPU</font>操作可以继续进行，<font face="Times New Roman">IO</font>操作完成后通过某种方法如事件通知程序，然后程序做相应的处理，流程如下：</font></div>
<div><font size="2">
<div align="center"><img src="http://blogimg.chinaunix.net/blog/upfile2/071108134919.jpg" onload="javascript:if(this.width  alt=" />500)this.width=500;" border="0" width="500"&gt;</div>
</font></div>
<div align="center"><font size="2">图<font face="Times New Roman">5</font></font></div>
<div><font size="2">以前需要<font face="Times New Roman">18ns</font>执行的程序，现在只需要<font face="Times New Roman">11ns</font>就可以完成，性能提升。</font></div>
<div><font size="2">常用的文件异步操作、网络异步操作都属于<font face="Times New Roman">IO</font>复用。</font></div>
<div><font size="2">使用<font face="Times New Roman">IO</font>复用后，程序通常只需要一个线程就可以完成所有的功能，减少操作系统线程间切换的开销，并且不需要线程间同步，但是<font face="Times New Roman">IO</font>复用需要使用特定的方法监视<font face="Times New Roman">IO</font>状态，开发相对比较复杂。</font></div>
<div><font size="2"><font face="Times New Roman">Window 2000</font>的<font face="Times New Roman">IOCP</font>（<font face="Times New Roman">IO Complete Port</font>）就是基于<font face="Times New Roman">IO</font>复用的思想。</font></div>
<h1><font size="4">总结</font></h1>
<div><font size="2">虽然上面的结论是在一个<font face="Times New Roman">CPU</font>并且没有考虑操作系统的进程调度和内存管理等因素的影响的前提下得出的，但是在以<font face="Times New Roman">CPU</font>为中心的计算机体系结构中，<font face="Times New Roman">CPU</font>操作和<font face="Times New Roman">IO</font>操作的划分确实普遍适用的，进程调度和内存管理本身也可以看成是<font face="Times New Roman">CPU</font>操作和<font face="Times New Roman">IO</font>操作复合的程序，对于多<font face="Times New Roman">CPU</font>的系统和多<font face="Times New Roman">IO</font>设备的系统，分析的基础是所有这些设备能并行操作，所以上面得出的结论是普遍适用的。</font></div>
<div><font size="2">在分析过程中，对很多结论使用了粗体字，是为了醒目，不要死记硬背，要记住的是基本原理和分析方法，这样才能放之四海而皆准。<br />
<br />
<br />
转自：http://blog.chinaunix.net/u1/52224/showart_417513.html<br />
</font></div>
<img src ="http://www.blogjava.net/shiliqiang/aggbug/288641.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shiliqiang/" target="_blank">石头@</a> 2009-07-27 21:37 <a href="http://www.blogjava.net/shiliqiang/articles/288641.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>文本处理(一)状态机(2) </title><link>http://www.blogjava.net/shiliqiang/articles/286321.html</link><dc:creator>石头@</dc:creator><author>石头@</author><pubDate>Fri, 10 Jul 2009 13:16:00 GMT</pubDate><guid>http://www.blogjava.net/shiliqiang/articles/286321.html</guid><wfw:comment>http://www.blogjava.net/shiliqiang/comments/286321.html</wfw:comment><comments>http://www.blogjava.net/shiliqiang/articles/286321.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shiliqiang/comments/commentRss/286321.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shiliqiang/services/trackbacks/286321.html</trackback:ping><description><![CDATA[<p>系统程序员成长计划-文本处理(一)</p>
<p>状态机(2)</p>
<p>o 用有穷状态机解一道面试题。</p>
<p>刚毕业的时候，我到一家外企面试，面试题里有这样一道题：</p>
<p>统计一篇英文文章里的单词个数。</p>
<p>有多种方法可以解这道题，这里我们选择用有穷状态机来解，做法如下：</p>
<p>先把这篇英文文章读入到一个缓冲区里，让一个指针从缓冲区的头部一直移到缓冲区的尾部，指针会处于两种状态：&#8220;单词内&#8221;或&#8220;单词外&#8221;，加上后面提到的初始状态和接受状态，就是有穷状态机的状态集。缓冲区中的字符集合就是有穷状态机的字母表。</p>
<p>如果当前状态为&#8220;单词内&#8221;，移到指针时，指针指向的字符是非单词字符(如标点和空格)，那状态会从&#8220;单词内&#8221;转换到&#8220;单词外&#8221;。如果当前状态为&#8220;单
词外&#8221;， 移到指针时，指针指向的字符是单词字符(如字母)，那状态会从&#8220;单词外&#8221;转换到&#8220;单词内&#8221;。这些转换规则就是状态转换函数。</p>
<p>指针指向缓冲区的头部时是初始状态。</p>
<p>指针指向缓冲区的尾部时是接受状态。</p>
<p>每次当状态从&#8220;单词内&#8221;转换到&#8220;单词外&#8221;时，单词计数增加一。<br />
这个有穷状态机的图形表示如下：</p>
<p><img src="http://www.limodev.cn/gallery/albums/blog-pictures/sysprog/simple_state.JPG" alt="" />
</p>
<p>下面我们看看程序怎么写：</p>
<pre>int count_word(const char* text)<br />
<br />
{<br />
<br />
/*定义各种状态，我们不关心接受状态，这里可以不用定义。*/<br />
<br />
enum _State<br />
<br />
{<br />
<br />
STAT_INIT,<br />
<br />
STAT_IN_WORD,<br />
<br />
STAT_OUT_WORD,<br />
<br />
}state = STAT_INIT;<br />
<br />
<br />
<br />
int count = 0;<br />
<br />
const char* p = text;<br />
<br />
<br />
<br />
/*在一个循环中，指针从缓冲区头移动缓冲区尾*/<br />
<br />
for(p = text; *p != '\0'; p++)<br />
<br />
{<br />
<br />
switch(state)<br />
<br />
{<br />
<br />
case STAT_INIT:<br />
<br />
{<br />
<br />
if(IS_WORD_CHAR(*p))<br />
<br />
{<br />
<br />
/*指针指向单词字符，状态转换为单词内*/<br />
<br />
state = STAT_IN_WORD;<br />
<br />
}<br />
<br />
else<br />
<br />
{<br />
<br />
/*指针指向非单词字符，状态转换为单词外*/<br />
<br />
state = STAT_OUT_WORD;<br />
<br />
}<br />
<br />
break;<br />
<br />
}<br />
<br />
case STAT_IN_WORD:<br />
<br />
{<br />
<br />
if(!IS_WORD_CHAR(*p))<br />
<br />
{<br />
<br />
/*指针指向非单词字符，状态转换为单词外，增加单词计数*/<br />
<br />
count++;<br />
<br />
state = STAT_OUT_WORD;<br />
<br />
}<br />
<br />
break;<br />
<br />
}<br />
<br />
case STAT_OUT_WORD:<br />
<br />
{<br />
<br />
if(IS_WORD_CHAR(*p))<br />
<br />
{<br />
<br />
/*指针指向单词字符，状态转换为单词内*/<br />
<br />
state = STAT_IN_WORD;<br />
<br />
}<br />
<br />
break;<br />
<br />
}<br />
<br />
default:break;<br />
<br />
}<br />
<br />
}<br />
<br />
<br />
<br />
if(state == STAT_IN_WORD)<br />
<br />
{<br />
<br />
/*如果由单词内进入接受状态，增加单词计数*/<br />
<br />
count++;<br />
<br />
}<br />
<br />
<br />
<br />
return count;<br />
<br />
}<br />
<br />
</pre>
<p>用状态机来解这道题目，思路清晰，程序简单，不易出错。</p>
<p>这道题目只是为了展示一些奇技淫巧，还是有一些实际用处呢？回答这个问题之前，我们先对上面的程序做点扩展，不只是统计单词的个数，而且要分离出里面的每个单词。</p>
<pre>int word_segmentation(const char* text, OnWordFunc on_word, void* ctx)<br />
<br />
{<br />
<br />
enum _State<br />
<br />
{<br />
<br />
STAT_INIT,<br />
<br />
STAT_IN_WORD,<br />
<br />
STAT_OUT_WORD,<br />
<br />
}state = STAT_INIT;<br />
<br />
<br />
<br />
int count = 0;<br />
<br />
char* copy_text = strdup(text);<br />
<br />
char* p = copy_text;<br />
<br />
char* word = copy_text;<br />
<br />
<br />
<br />
for(p = copy_text; *p != '\0'; p++)<br />
<br />
{<br />
<br />
switch(state)<br />
<br />
{<br />
<br />
case STAT_INIT:<br />
<br />
{<br />
<br />
if(IS_WORD_CHAR(*p))<br />
<br />
{<br />
<br />
word = p;<br />
<br />
state = STAT_IN_WORD;<br />
<br />
}<br />
<br />
break;<br />
<br />
}<br />
<br />
case STAT_IN_WORD:<br />
<br />
{<br />
<br />
if(!IS_WORD_CHAR(*p))<br />
<br />
{<br />
<br />
count++;<br />
<br />
*p = '\0';<br />
<br />
on_word(ctx, word);<br />
<br />
state = STAT_OUT_WORD;<br />
<br />
}<br />
<br />
break;<br />
<br />
}<br />
<br />
case STAT_OUT_WORD:<br />
<br />
{<br />
<br />
if(IS_WORD_CHAR(*p))<br />
<br />
{<br />
<br />
word = p;<br />
<br />
state = STAT_IN_WORD;<br />
<br />
}<br />
<br />
break;<br />
<br />
}<br />
<br />
default:break;<br />
<br />
}<br />
<br />
}<br />
<br />
<br />
<br />
if(state == STAT_IN_WORD)<br />
<br />
{<br />
<br />
count++;<br />
<br />
on_word(ctx, word);<br />
<br />
}<br />
<br />
<br />
<br />
free(copy_text);<br />
<br />
<br />
<br />
return count;<br />
<br />
}<br />
<br />
</pre>
<p>状态机不变，只是在状态转换时，做是事情不一样。这里从&#8220;单词内&#8221;转换到其它状态时，增加单词计数，并分离出当前的单词。至于拿分离出的单词来做什么，由传入的回调函数决定，比如可以用来统计每个单词出现的频率。</p>
<p>但如果讨论还是限于英文文章，这个程序的意义仍然不大，现在来做进一步扩展。我们考虑的文本不再是英文文章，而是一些文本数据，这些数据由一些分隔符分开，我们把数据称为token，现在我们要把这些token分离出来。</p>
<pre>typedef void (*OnTokenFunc)(void* ctx, int index, const char* token);<br />
<br />
<br />
<br />
#define IS_DELIM(c) (strchr(delims, c) != NULL)<br />
<br />
int parse_token(const char* text, const char* delims, OnTokenFunc on_token, void* ctx)<br />
<br />
{<br />
<br />
enum _State<br />
<br />
{<br />
<br />
STAT_INIT,<br />
<br />
STAT_IN,<br />
<br />
STAT_OUT,<br />
<br />
}state = STAT_INIT;<br />
<br />
<br />
<br />
int   count     = 0;<br />
<br />
char* copy_text = strdup(text);<br />
<br />
char* p         = copy_text;<br />
<br />
char* token     = copy_text;<br />
<br />
<br />
<br />
for(p = copy_text; *p != '\0'; p++)<br />
<br />
{<br />
<br />
switch(state)<br />
<br />
{<br />
<br />
case STAT_INIT:<br />
<br />
case STAT_OUT:<br />
<br />
{<br />
<br />
if(!IS_DELIM(*p))<br />
<br />
{<br />
<br />
token = p;<br />
<br />
state = STAT_IN;<br />
<br />
}<br />
<br />
break;<br />
<br />
}<br />
<br />
case STAT_IN:<br />
<br />
{<br />
<br />
if(IS_DELIM(*p))<br />
<br />
{<br />
<br />
*p = '\0';<br />
<br />
on_token(ctx, count++, token);<br />
<br />
state = STAT_OUT;<br />
<br />
}<br />
<br />
break;<br />
<br />
}<br />
<br />
default:break;<br />
<br />
}<br />
<br />
}<br />
<br />
<br />
<br />
if(state == STAT_IN)<br />
<br />
{<br />
<br />
on_token(ctx, count++, token);<br />
<br />
}<br />
<br />
<br />
<br />
on_token(ctx, -1, NULL);<br />
<br />
free(copy_text);<br />
<br />
<br />
<br />
return count;<br />
<br />
}<br />
<br />
</pre>
<p>用分隔符分隔的文本数据有很多，如：</p>
<p>环境PATH，它由&#8216;:&#8217;分开的多个路径组成。如：<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</p>
<p>文件名，它由&#8216;/&#8217;分开的路径组成。如：<br />
/usr/lib/qt-3.3/bin</p>
<p>URL中的参数，它&#8216;&amp;&#8217;分开的多个key/value对组成。<br />
hl=zh-CN&amp;q=limodev&amp;btnG=Google+搜索&amp;meta=&amp;aq=f&amp;oq=</p>
<p>所有这些数据都可以用上面的函数处理，所以这个小函数是颇具实用价值的。</p>
<img src ="http://www.blogjava.net/shiliqiang/aggbug/286321.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shiliqiang/" target="_blank">石头@</a> 2009-07-10 21:16 <a href="http://www.blogjava.net/shiliqiang/articles/286321.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>文本处理(一)状态机(1)</title><link>http://www.blogjava.net/shiliqiang/articles/286310.html</link><dc:creator>石头@</dc:creator><author>石头@</author><pubDate>Fri, 10 Jul 2009 11:37:00 GMT</pubDate><guid>http://www.blogjava.net/shiliqiang/articles/286310.html</guid><wfw:comment>http://www.blogjava.net/shiliqiang/comments/286310.html</wfw:comment><comments>http://www.blogjava.net/shiliqiang/articles/286310.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shiliqiang/comments/commentRss/286310.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shiliqiang/services/trackbacks/286310.html</trackback:ping><description><![CDATA[<p>系统程序员成长计划-文本处理(一)</p>
<p>状态机(1)</p>
<p>o 有穷状态机的形式定义</p>
<p>有穷状态机是一个五元组 (Q，&#931;，&#948;，q0，F)，其中：<br />
Q是一个有穷集合，称为状态集。<br />
&#931;是一个有穷集合，称为字母表。<br />
&#948;: Q x&#931;Q称为状态转移函数。<br />
q0 是初始状态。<br />
F 是接受状态集。</p>
<p>教科书上是这样定义有穷自动机的，这个形式定义精确的描述了有穷状态机的含义。但是大部分人(包括我自己)第一次看到它时，反复的读上几遍，仍然不知道它在说什么。幸好通过一些实例，我们可以很容易明白有穷状态机的原理。</p>
<p>自动门是一个典型的有穷状态机：</p>
<p>它有&#8220;开&#8221;和&#8220;关&#8221;两种状态，这就是它的状态集，也就是上面所说的Q。</p>
<p>人可以从自动门进来或出去，当人进来或出去的时候，自动门会自动打开，如果在规定的时间内没有人进出，自动门会自动关上。人的进来、出去和超时三个事件是自动门的字母表，也就是上面所说的&#931;。而自动门在当前状态下，对事件的响应，会引起状态的变化，这就是状态转换函数，也就是上面所说的&#948;。</p>
<p>自动门刚安装好的时候，我们可以认为它是关上的，所以关闭状态是自动门的初始状态。</p>
<p>在理想情况下，自动门会一直运行，所以它没有接受状态，接受状态集F是空集。</p>
<p>有穷状态机的形式定义很精确，文字描述比较通俗，而图形表示则比较直观。通用建模语言（UML）里的状态图是状态机的常用图形表示方法。简单的状态图包括一些状态，用圆角方框表示，里面有状态的名称。状态之间的转换，用箭头表示，上面可以加转换条件。自动门的状态机可以用下图表示：</p>
<p><img alt="" src="http://www.limodev.cn/gallery/albums/blog-pictures/sysprog/autogate_state.JPG" /> </p>
<p>有穷状态机很简单，在生活中可以找出很多这样的例子。但是教科书里讲得太复杂了，一会儿证明确定性有穷状态机和非确定性有穷状态机的等价性，一会儿证明正则表达式的正则运算是封闭的，一会儿又来个泵引理。花了很长时间，我才明白这些原理，但两年之后，我又把它们忘得一干二净。</p>
<p>主要原因是工作中没有机会运用它们，这些理论的证明于编程没有太大用处，不过状态机本身却是文本处理利器，由于程序员在很多场合下都是在与文本数据打交道，所以状态机是程序员必备的工具之一。这里我们将一起学习如何用状态机来处理文本数据，后面我们也会提到状态机的其它用途，不过不是本节的重点。<br />
<br />
<br />
<br />
文章出处：<a href="http://www.limodev.cn/blog">http://www.limodev.cn/blog</a> <br />
作者联系方式：李先静 &lt;xianjimli at hotmail dot com&gt;</p>
<img src ="http://www.blogjava.net/shiliqiang/aggbug/286310.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shiliqiang/" target="_blank">石头@</a> 2009-07-10 19:37 <a href="http://www.blogjava.net/shiliqiang/articles/286310.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>文本处理(二)</title><link>http://www.blogjava.net/shiliqiang/articles/286307.html</link><dc:creator>石头@</dc:creator><author>石头@</author><pubDate>Fri, 10 Jul 2009 11:03:00 GMT</pubDate><guid>http://www.blogjava.net/shiliqiang/articles/286307.html</guid><wfw:comment>http://www.blogjava.net/shiliqiang/comments/286307.html</wfw:comment><comments>http://www.blogjava.net/shiliqiang/articles/286307.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shiliqiang/comments/commentRss/286307.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shiliqiang/services/trackbacks/286307.html</trackback:ping><description><![CDATA[<p><br />
&nbsp;</p>
<p>Builder模式</p>
<p>前面我们学习了状态机，并利用它来解析各种格式的文本数据。解析过程把线性的文本数据转换成一些基本的逻辑单元，但这通常只是任务的一部分，接下来我们还要对这些解析出来的数据进一步处理。对于特定格式的文本数据，它的解析过程是一样的，但是对解析出来的数据的处理却是多种多样的。为了让解析过程能被重用，就需要把数据的解析和数据的处理分开。</p>
<p>现在我们回过头来看一下前面写的函数parse_token，这个函数把用分隔符分隔的文本数据，分离出一个一个的token。</p>
<p>parse_token的函数原型如下：</p>
<p>typedef void (*OnTokenFunc)(void* ctx, int index, const char* token);<br />
int parse_token(const char* text, const char* delims, OnTokenFunc on_token, void* ctx)</p>
<p>parse_token负责解析数据，但它并不关心数据代表的意义及用途。对数据的进一步处理由调用者提供的回调函数来完成，函数 parse_token每解析到一个token，就调用这个回调函数。parse_token负责数据的解析，回调函数负责数据的处理，这样一来，数据的解析和数据的处理就分开了。</p>
<p>parse_token可以认为是Builder模式最朴素的应用。现在我们看看Builder 模式：</p>
<p>Builder 模式的意图：将一个复杂对象的构建与它的表示分离，使得同样的构建过程可以创建不同的表示。&#8220;构建&#8221;其实就是前面的解析过程，而&#8220;表示&#8221;就是前面说的对数据的处理。</p>
<p>对象关系：<br />
<img alt="对象关系" src="http://www.limodev.cn/gallery/albums/blog-pictures/o_builder_pattern.jpg" /> <br />
上面的parse_token与这里的Director对应。</p>
<p>上面的回调函数与这里的Builder对应。</p>
<p>具体的回调函数与这里的ConcreteBuilder对应。</p>
<p>对数据处理的结果就是Product。</p>
<p>对象协作：<br />
<img alt="对象协作" src="http://www.limodev.cn/gallery/albums/blog-pictures/o_builder_pattern_interaction.jpg" /> <br />
Client是parse_token的调用者。</p>
<p>由于parse_token是按面向过程的方式设计的，所以ConcreteBuilder和Director的创建只是对应于一些初始化代码。</p>
<p>调用parse_token相当于调用aDirector的Construct函数。</p>
<p>调用回调函数相当于调用aConcreteBuilder的BuildPart函数。</p>
<p>回调函数可能把处理结果存在它的参数ctx中，GetResult是从里面获取结果，这是可选的过程，依赖于具体回调函数所做的工作。</p>
<p>parse_token的例子简单直接，对于理解Builder模式有较大的帮助，不过毕竟它是面向过程的。现在我们以前面的XML解析器为例来说明Builder模式，虽然我们的代码是用C写的，但完全是用面向对象的思想来设计的。Builder是一个接口，我们先把它定义出来：</p>
<pre>struct _XmlBuilder;<br />
typedef struct _XmlBuilder XmlBuilder; <br />
<br />
typedef void (*XmlBuilderOnStartElementFunc)(XmlBuilder* thiz, const char* tag, const char** attrs);<br />
typedef void (*XmlBuilderOnEndElementFunc)(XmlBuilder* thiz, const char* tag);<br />
typedef void (*XmlBuilderOnTextFunc)(XmlBuilder* thiz, const char* text, size_t length);<br />
typedef void (*XmlBuilderOnCommentFunc)(XmlBuilder* thiz, const char* text, size_t length);<br />
typedef void (*XmlBuilderOnPiElementFunc)(XmlBuilder* thiz, const char* tag, const char** attrs);<br />
typedef void (*XmlBuilderOnErrorFunc)(XmlBuilder* thiz, int line, int row, const char* message);<br />
typedef void (*XmlBuilderDestroyFunc)(XmlBuilder* thiz); <br />
<br />
struct _XmlBuilder<br />
{<br />
XmlBuilderOnStartElementFunc on_start_element;<br />
XmlBuilderOnEndElementFunc   on_end_element;<br />
XmlBuilderOnTextFunc         on_text;<br />
XmlBuilderOnCommentFunc      on_comment;<br />
XmlBuilderOnPiElementFunc    on_pi_element;<br />
XmlBuilderOnErrorFunc        on_error;<br />
XmlBuilderDestroyFunc        destroy; <br />
<br />
char priv[1];<br />
}; <br />
<br />
static inline void xml_builder_on_start_element(XmlBuilder* thiz, const char* tag, const char** attrs)<br />
<br />
{<br />
<br />
return_if_fail(thiz != NULL &amp;&amp; thiz-&gt;on_start_element != NULL);<br />
<br />
thiz-&gt;on_start_element(thiz, tag, attrs);<br />
<br />
return;<br />
<br />
}<br />
<br />
static inline void xml_builder_on_end_element(XmlBuilder* thiz, const char* tag)<br />
<br />
{<br />
<br />
return_if_fail(thiz != NULL &amp;&amp; thiz-&gt;on_end_element != NULL);<br />
<br />
thiz-&gt;on_end_element(thiz, tag);<br />
<br />
return;<br />
<br />
}<br />
<br />
...<br />
(其它inline函数不列在这里了)<br />
</pre>
<p>XmlBuilder接口要求实现下列函数：</p>
<p>on_start_element：解析器解析到一个起始TAG时调用它。<br />
on_end_element：解析器解析到一个结束TAG时调用它。<br />
on_text：解析器解析到一段文本时调用它。<br />
on_comment：解析器解析到一个注释时调用它。<br />
on_pi_element：解析器解析到一个处理指令时调用它。<br />
on_error：解析器遇到错误时调用它。<br />
destroy：用销毁Builder对象。</p>
<p>on_start_element和on_end_element等函数相当于Builder模式中的BuildPartX函数。</p>
<p>XML解析器相当于Director，在前面我们已经写好了，不过它对解析出来的数据没有做任何处理。现在我们对它做些修改，让它调用XmlBuilder的函数。</p>
<p>XML解析器对外提供下面几个函数：</p>
<p>o 构造函数。</p>
<pre>XmlParser* xml_parser_create(void);<br />
</pre>
<p>o 为xmlParser设置builder对象。</p>
<pre>void       xml_parser_set_builder(XmlParser* thiz, XmlBuilder* builder);<br />
</pre>
<p>o 解析XML</p>
<pre>void       xml_parser_parse(XmlParser* thiz, const char* xml);<br />
</pre>
<p>o 析构函数</p>
<pre>void       xml_parser_destroy(XmlParser* thiz);<br />
</pre>
<p>在解析时，解析到相应的tag，就调用XmlBuilder相应的函数：</p>
<p>o 解析到起始tag时调用xml_builder_on_start_element</p>
<pre>static void xml_parser_parse_start_tag(XmlParser* thiz)<br />
{<br />
enum _State<br />
{<br />
STAT_NAME,<br />
STAT_ATTR,<br />
STAT_END,<br />
}state = STAT_NAME; <br />
<br />
char* tag_name = NULL;<br />
const char* start = thiz-&gt;read_ptr - 1; <br />
<br />
for(; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++)<br />
{<br />
char c = *thiz-&gt;read_ptr; <br />
<br />
switch(state)<br />
{<br />
case STAT_NAME:<br />
{<br />
if(isspace(c) || c == '&gt;' || c == '/')<br />
{<br />
tag_name = (char*)xml_parser_strdup(thiz, start, thiz-&gt;read_ptr - start);<br />
state = (c != '&gt;' &amp;&amp; c != '/') ? STAT_ATTR : STAT_END;<br />
}<br />
break;<br />
}<br />
case STAT_ATTR:<br />
{<br />
xml_parser_parse_attrs(thiz, '/');<br />
state = STAT_END; <br />
<br />
break;<br />
}<br />
default:break;<br />
} <br />
<br />
if(state == STAT_END)<br />
{<br />
break;<br />
}<br />
} <br />
<br />
tag_name = thiz-&gt;buffer + (size_t)tag_name;<br />
/*解析完成，调用builder的函数xml_builder_on_start_element。*/<br />
xml_builder_on_start_element(thiz-&gt;builder, tag_name, (const char**)thiz-&gt;attrs); <br />
<br />
if(thiz-&gt;read_ptr[0] == '/')<br />
{<br />
/*如果tag以'/'结束，调用builder的函数xml_builder_on_end_element。*/<br />
xml_builder_on_end_element(thiz-&gt;builder, tag_name);<br />
} <br />
<br />
for(; *thiz-&gt;read_ptr != '&gt;' &amp;&amp; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++); <br />
<br />
return;<br />
}<br />
</pre>
<p>o 解析到结束tag时调用xml_builder_on_end_element</p>
<pre>static void xml_parser_parse_end_tag(XmlParser* thiz)<br />
{<br />
char* tag_name = NULL;<br />
const char* start = thiz-&gt;read_ptr;<br />
for(; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++)<br />
{<br />
if(*thiz-&gt;read_ptr == '&gt;')<br />
{<br />
tag_name = thiz-&gt;buffer + xml_parser_strdup(thiz, start, thiz-&gt;read_ptr-start);<br />
/*解析完成，调用builder的函数xml_builder_on_end_element。*/<br />
xml_builder_on_end_element(thiz-&gt;builder, tag_name); <br />
<br />
break;<br />
}<br />
} <br />
<br />
return;<br />
}<br />
</pre>
<p>o 解析到文本时调用xml_builder_on_text</p>
<pre>static void xml_parser_parse_text(XmlParser* thiz)<br />
{<br />
const char* start = thiz-&gt;read_ptr - 1;<br />
for(; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++)<br />
{<br />
char c = *thiz-&gt;read_ptr; <br />
<br />
if(c == '&lt;')<br />
{<br />
if(thiz-&gt;read_ptr &gt; start)<br />
{<br />
/*解析完成，调用builder的函数xml_builder_on_text。*/<br />
xml_builder_on_text(thiz-&gt;builder, start, thiz-&gt;read_ptr-start);<br />
}<br />
thiz-&gt;read_ptr--;<br />
return;<br />
}<br />
else if(c == '&amp;')<br />
{<br />
xml_parser_parse_entity(thiz);<br />
}<br />
} <br />
<br />
return;<br />
}<br />
</pre>
<p>o 解析到注释时调用xml_builder_on_comment</p>
<pre>static void xml_parser_parse_comment(XmlParser* thiz)<br />
{<br />
enum _State<br />
{<br />
STAT_COMMENT,<br />
STAT_MINUS1,<br />
STAT_MINUS2,<br />
}state = STAT_COMMENT; <br />
<br />
const char* start = ++thiz-&gt;read_ptr;<br />
for(; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++)<br />
{<br />
char c = *thiz-&gt;read_ptr; <br />
<br />
switch(state)<br />
{<br />
case STAT_COMMENT:<br />
{<br />
if(c == '-')<br />
{<br />
state = STAT_MINUS1;<br />
}<br />
break;<br />
}<br />
case STAT_MINUS1:<br />
{<br />
if(c == '-')<br />
{<br />
state = STAT_MINUS2;<br />
}<br />
else<br />
{<br />
state = STAT_COMMENT;<br />
}<br />
break;<br />
}<br />
case STAT_MINUS2:<br />
{<br />
if(c == '&gt;')<br />
{<br />
/*解析完成，调用builder的函数xml_builder_on_comment。*/<br />
xml_builder_on_comment(thiz-&gt;builder, start, thiz-&gt;read_ptr-start-2);<br />
return;<br />
}<br />
}<br />
default:break;<br />
}<br />
} <br />
<br />
return;<br />
}<br />
</pre>
<p>o 解析到处理指令时调用xml_builder_on_pi_element</p>
<pre>static void xml_parser_parse_pi(XmlParser* thiz)<br />
{<br />
enum _State<br />
{<br />
STAT_NAME,<br />
STAT_ATTR,<br />
STAT_END<br />
}state = STAT_NAME; <br />
<br />
char* tag_name = NULL;<br />
const char* start = thiz-&gt;read_ptr; <br />
<br />
for(; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++)<br />
{<br />
char c = *thiz-&gt;read_ptr; <br />
<br />
switch(state)<br />
{<br />
case STAT_NAME:<br />
{<br />
if(isspace(c) || c == '&gt;')<br />
{<br />
tag_name = (char*)xml_parser_strdup(thiz, start, thiz-&gt;read_ptr - start);<br />
state = c != '&gt;' ? STAT_ATTR : STAT_END;<br />
} <br />
<br />
break;<br />
}<br />
case STAT_ATTR:<br />
{<br />
xml_parser_parse_attrs(thiz, '?');<br />
state = STAT_END;<br />
break;<br />
}<br />
default:break;<br />
} <br />
<br />
if(state == STAT_END)<br />
{<br />
break;<br />
}<br />
} <br />
<br />
tag_name = thiz-&gt;buffer + (size_t)tag_name;<br />
/*解析完成，调用builder的函数xml_builder_on_pi_element。*/<br />
xml_builder_on_pi_element(thiz-&gt;builder, tag_name, (const char**)thiz-&gt;attrs);	 <br />
<br />
for(; *thiz-&gt;read_ptr != '&gt;' &amp;&amp; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++); <br />
<br />
return;<br />
}<br />
</pre>
<p>从上面的代码可以看出，XmlParser在适当的时候调用了XmlBuilder的接口函数，至于XmlBuilder在这些函数里做什么，要看具体的Builder实现了。</p>
<p>先看一个最简单的XmlBuilder实现，它只是在屏幕上打印出传递给它的数据：</p>
<p>o 创建函数</p>
<pre>XmlBuilder* xml_builder_dump_create(FILE* fp)<br />
{<br />
XmlBuilder* thiz = (XmlBuilder*)calloc(1, sizeof(XmlBuilder)); <br />
<br />
if(thiz != NULL)<br />
{<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv; <br />
<br />
thiz-&gt;on_start_element   = xml_builder_dump_on_start_element;<br />
thiz-&gt;on_end_element    = xml_builder_dump_on_end_element;<br />
thiz-&gt;on_text                  = xml_builder_dump_on_text;<br />
thiz-&gt;on_comment         = xml_builder_dump_on_comment;<br />
thiz-&gt;on_pi_element      = xml_builder_dump_on_pi_element;<br />
thiz-&gt;on_error                = xml_builder_dump_on_error;<br />
thiz-&gt;destroy                  = xml_builder_dump_destroy; <br />
<br />
priv-&gt;fp = fp != NULL ? fp : stdout;<br />
} <br />
<br />
return thiz;<br />
}<br />
</pre>
<p>和其它接口的创建函数一样，它只是把接口要求的函数指针指到具体的实现函数上。</p>
<p>o 实现 on_start_element</p>
<pre>static void xml_builder_dump_on_start_element(XmlBuilder* thiz, const char* tag, const char** attrs)<br />
{<br />
int i = 0;<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv;<br />
fprintf(priv-&gt;fp, "&lt;%s", tag); <br />
<br />
for(i = 0; attrs != NULL &amp;&amp; attrs[i] != NULL &amp;&amp; attrs[i + 1] != NULL; i += 2)<br />
{<br />
fprintf(priv-&gt;fp, " %s=\"%s\"", attrs[i], attrs[i + 1]);<br />
}<br />
fprintf(priv-&gt;fp, "&gt;"); <br />
<br />
return;<br />
}<br />
</pre>
<p>o 实现on_end_element</p>
<pre>static void xml_builder_dump_on_end_element(XmlBuilder* thiz, const char* tag)<br />
{<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv;<br />
fprintf(priv-&gt;fp, "<!-- %s-->\n", tag); <br />
<br />
return;<br />
}<br />
</pre>
<p>o 实现on_text</p>
<pre>static void xml_builder_dump_on_text(XmlBuilder* thiz, const char* text, size_t length)<br />
{<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv;<br />
fwrite(text, length, 1, priv-&gt;fp); <br />
<br />
return;<br />
}<br />
</pre>
<p>o 实现on_comment</p>
<pre>static void xml_builder_dump_on_comment(XmlBuilder* thiz, const char* text, size_t length)<br />
{<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv;<br />
fprintf(priv-&gt;fp, "<!-- ");
	fwrite(text, length, 1, priv->fp);
	fprintf(priv->fp, "-->\n"); <br />
<br />
return;<br />
}<br />
</pre>
<p>o 实现on_pi_element</p>
<pre>static void xml_builder_dump_on_pi_element(XmlBuilder* thiz, const char* tag, const char** attrs)<br />
{<br />
int i = 0;<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv;<br />
fprintf(priv-&gt;fp, "fp, " %s=\"%s\"", attrs[i], attrs[i + 1]);<br />
}<br />
fprintf(priv-&gt;fp, "?&gt;\n"); <br />
<br />
return;<br />
}<br />
</pre>
<p>o 实现on_error</p>
<pre>static void xml_builder_dump_on_error(XmlBuilder* thiz, int line, int row, const char* message)<br />
{<br />
fprintf(stderr, "(%d,%d) %s\n", line, row, message); <br />
<br />
return;<br />
}<br />
</pre>
<p>上面的XmlBuilder实现简单，而且有一定的实用价值，我一般都会先写这样一个Builder。它不但对于调试程序有不小的帮助，而且只要稍做修改，就可以把它改进成一个美化数据格式的小工具，不管原始数据的格式(当然要合符相应的语法规则)有多乱，你都能以一种比较好看的方式打印出来。</p>
<p>下面我们再看一个比较复杂的XmlBuilder的实现，它根据接收的数据构建一棵XML树。</p>
<p>o 创建函数</p>
<pre>XmlBuilder* xml_builder_tree_create(void)<br />
{<br />
XmlBuilder* thiz = (XmlBuilder*)calloc(1, sizeof(XmlBuilder)); <br />
<br />
if(thiz != NULL)<br />
{<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv; <br />
<br />
thiz-&gt;on_start_element   = xml_builder_tree_on_start_element;<br />
thiz-&gt;on_end_element    = xml_builder_tree_on_end_element;<br />
thiz-&gt;on_text                  = xml_builder_tree_on_text;<br />
thiz-&gt;on_comment         = xml_builder_tree_on_comment;<br />
thiz-&gt;on_pi_element      = xml_builder_tree_on_pi_element;<br />
thiz-&gt;on_error                = xml_builder_tree_on_error;<br />
thiz-&gt;destroy                  = xml_builder_tree_destroy; <br />
<br />
priv-&gt;root = xml_node_create_normal("__root__", NULL);<br />
priv-&gt;current = priv-&gt;root;<br />
} <br />
<br />
return thiz;<br />
}<br />
</pre>
<p>和其它接口的创建函数一样，它只是把接口要求的函数指针指到具体的实现函数上。这里还创建了一个根结点__root__，以保证整棵树只有一个根结点。</p>
<p>o 实现 on_start_element</p>
<pre>static void xml_builder_tree_on_start_element(XmlBuilder* thiz, const char* tag, const char** attrs)<br />
{<br />
XmlNode* new_node = NULL;<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv; <br />
<br />
new_node = xml_node_create_normal(tag, attrs);<br />
xml_node_append_child(priv-&gt;current, new_node);<br />
priv-&gt;current = new_node; <br />
<br />
return;<br />
}<br />
</pre>
<p>这里创建了一个新的结点，并追加为priv-&gt;current的子结点，然后让priv-&gt;current指向新的结点。</p>
<p>o 实现 on_end_element</p>
<pre>static void xml_builder_tree_on_end_element(XmlBuilder* thiz, const char* tag)<br />
{<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv;<br />
priv-&gt;current = priv-&gt;current-&gt;parent;<br />
assert(priv-&gt;current != NULL); <br />
<br />
return;<br />
}<br />
</pre>
<p>这里只是让priv-&gt;current指向它的父结点。</p>
<p>o 实现 on_text</p>
<pre>static void xml_builder_tree_on_text(XmlBuilder* thiz, const char* text, size_t length)<br />
{<br />
XmlNode* new_node = NULL;<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv; <br />
<br />
new_node = xml_node_create_text(text);<br />
xml_node_append_child(priv-&gt;current, new_node); <br />
<br />
return;<br />
}<br />
</pre>
<p>这里创建一个文本结点， 并追加为priv-&gt;current的子结点。</p>
<p>o 实现 on_comment</p>
<pre>static void xml_builder_tree_on_comment(XmlBuilder* thiz, const char* text, size_t length)<br />
{<br />
XmlNode* new_node = NULL;<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv; <br />
<br />
new_node = xml_node_create_comment(text);<br />
xml_node_append_child(priv-&gt;current, new_node); <br />
<br />
return;<br />
}<br />
</pre>
<p>这里创建一个注释结点， 并追加为priv-&gt;current的子结点。</p>
<p>o 实现 on_pi_element</p>
<pre>static void xml_builder_tree_on_pi_element(XmlBuilder* thiz, const char* tag, const char** attrs)<br />
{<br />
XmlNode* new_node = NULL;<br />
PrivInfo* priv = (PrivInfo*)thiz-&gt;priv; <br />
<br />
new_node = xml_node_create_pi(tag, attrs);<br />
xml_node_append_child(priv-&gt;current, new_node); <br />
<br />
return;<br />
}<br />
</pre>
<p>这里创建一个处理指令结点， 并追加为priv-&gt;current的子结点。</p>
<p>o 实现on_error</p>
<pre>static void xml_builder_tree_on_error(XmlBuilder* thiz, int line, int row, const char* message)<br />
{<br />
fprintf(stderr, "(%d,%d) %s\n", line, row, message); <br />
<br />
return;<br />
}<br />
</pre>
<p>下面我们再看XmlNode的数据结构和主要函数：</p>
<p>o 数据结构</p>
<pre>typedef struct _XmlNode<br />
{<br />
XmlNodeType type;<br />
union<br />
{<br />
char* text;<br />
char* comment;<br />
XmlNodePi pi;<br />
XmlNodeNormal normal;<br />
}u;<br />
struct _XmlNode* parent;<br />
struct _XmlNode* children;<br />
struct _XmlNode* sibling;<br />
}XmlNode;<br />
</pre>
<p>type决定了结点的类型，可以是处理指令(XML_NODE_PI)、文本(XML_NODE_TEXT)、注释(XML_NODE_COMMENT)或普通TAG(XML_NODE_NORMAL)。</p>
<p>联合体用于存放具体结点信息。</p>
<p>parent指向父结点。</p>
<p>children指向第一个子结点。</p>
<p>sibling指向下一个兄弟结点。</p>
<p>o 创建普通TAG结点</p>
<pre>XmlNode* xml_node_create_normal(const char* name, const char** attrs)<br />
{<br />
XmlNode* node = NULL;<br />
return_val_if_fail(name != NULL, NULL);<br />
<br />
if((node = calloc(1, sizeof(XmlNode))) != NULL)<br />
{<br />
int i = 0;<br />
node-&gt;type = XML_NODE_NORMAL;<br />
node-&gt;u.normal.name = strdup(name);<br />
<br />
if(attrs != NULL)<br />
{<br />
for(i = 0; attrs[i] != NULL &amp;&amp; attrs[i+1] != NULL; i += 2)<br />
{<br />
xml_node_append_attr(node, attrs[i], attrs[i+1]);<br />
}<br />
}<br />
}<br />
<br />
return node;<br />
}<br />
</pre>
<p>o 创建处理指令结点</p>
<pre>XmlNode* xml_node_create_pi(const char* name, const char** attrs)<br />
{<br />
XmlNode* node = NULL;<br />
return_val_if_fail(name != NULL, NULL);<br />
<br />
if((node = calloc(1, sizeof(XmlNode))) != NULL)<br />
{<br />
int i = 0;<br />
node-&gt;type = XML_NODE_PI;<br />
node-&gt;u.pi.name = strdup(name);<br />
if(attrs != NULL)<br />
{<br />
for(i = 0; attrs[i] != NULL &amp;&amp; attrs[i+1] != NULL; i += 2)<br />
{<br />
xml_node_append_attr(node, attrs[i], attrs[i+1]);<br />
}<br />
}<br />
}<br />
<br />
return node;<br />
}<br />
</pre>
<p>o 创建文本结点</p>
<pre>XmlNode* xml_node_create_text(const char* text)<br />
{<br />
XmlNode* node = NULL;<br />
return_val_if_fail(text != NULL, NULL);<br />
<br />
if((node = calloc(1, sizeof(XmlNode))) != NULL)<br />
{<br />
node-&gt;type = XML_NODE_TEXT;<br />
node-&gt;u.text = strdup(text);<br />
}<br />
<br />
return node;<br />
}<br />
</pre>
<p>o 创建注释结点</p>
<pre>XmlNode* xml_node_create_comment(const char* comment)<br />
{<br />
XmlNode* node = NULL;<br />
return_val_if_fail(comment != NULL, NULL);<br />
<br />
if((node = calloc(1, sizeof(XmlNode))) != NULL)<br />
{<br />
node-&gt;type = XML_NODE_COMMENT;<br />
node-&gt;u.comment = strdup(comment);<br />
}<br />
<br />
return node;<br />
}<br />
</pre>
<p>o 追加一个兄弟结点</p>
<pre>XmlNode* xml_node_append_sibling(XmlNode* node, XmlNode* sibling)<br />
{<br />
return_val_if_fail(node != NULL &amp;&amp; sibling != NULL, NULL);<br />
<br />
if(node-&gt;sibling == NULL)<br />
{<br />
/*没有兄弟结点，让兄弟结点指向sibling */<br />
node-&gt;sibling = sibling;<br />
}<br />
else<br />
{<br />
/*否则，把sibling追加为最后一个兄弟结点*/<br />
XmlNode* iter = node-&gt;sibling;<br />
while(iter-&gt;sibling != NULL) iter = iter-&gt;sibling;<br />
iter-&gt;sibling = sibling;<br />
}<br />
/*让兄弟结点的父结点指向自己的父结点*/<br />
<br />
sibling-&gt;parent = node-&gt;parent;<br />
<br />
return sibling;<br />
}<br />
</pre>
<p>o 追加一个子结点</p>
<pre>XmlNode* xml_node_append_child(XmlNode* node, XmlNode* child)<br />
{<br />
return_val_if_fail(node != NULL &amp;&amp; child != NULL, NULL);<br />
<br />
if(node-&gt;children == NULL)<br />
{<br />
/*没有子结点，让子结点指向child */<br />
node-&gt;children = child;<br />
}<br />
else<br />
{<br />
/*否则，把child 追加为最后一个子结点*/<br />
XmlNode* iter = node-&gt;children;<br />
while(iter-&gt;sibling != NULL) iter = iter-&gt;sibling;<br />
iter-&gt;sibling = child;<br />
}<br />
/*让子结点的父结点指向自己*/<br />
<br />
child-&gt;parent = node;<br />
<br />
return child;<br />
}<br />
</pre>
<p>回头再看一下XmlParser，XmlBuilder及几个具体的XmlBuilder的实现，我们可以看到，它们的实现都非常简单，其实这完全得益于Builder模式的设计方法。它利用分而治之的思想，把数据的解析和数据的处理分开，降低了实现的复杂度。其次它利用了抽象的思想，从而数据的解析只关心处理数据处理的接口，而不关心的它的实现，使得数据解析和数据处理可以独立变化。</p>
<p>分而治之和抽象是降低复杂度最有效的手段之一，它们在Builder模式里得到了很好的体现。初学者应该多花些时间去体会。<br />
<br />
<br />
文章出处：<a href="http://www.limodev.cn/blog">http://www.limodev.cn/blog</a> <br />
作者联系方式：李先静 &lt;xianjimli at hotmail dot com&gt;</p>
<img src ="http://www.blogjava.net/shiliqiang/aggbug/286307.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shiliqiang/" target="_blank">石头@</a> 2009-07-10 19:03 <a href="http://www.blogjava.net/shiliqiang/articles/286307.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>文本处理(一)</title><link>http://www.blogjava.net/shiliqiang/articles/286306.html</link><dc:creator>石头@</dc:creator><author>石头@</author><pubDate>Fri, 10 Jul 2009 10:57:00 GMT</pubDate><guid>http://www.blogjava.net/shiliqiang/articles/286306.html</guid><wfw:comment>http://www.blogjava.net/shiliqiang/comments/286306.html</wfw:comment><comments>http://www.blogjava.net/shiliqiang/articles/286306.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shiliqiang/comments/commentRss/286306.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shiliqiang/services/trackbacks/286306.html</trackback:ping><description><![CDATA[<p>文章出处：<a href="http://www.limodev.cn/blog">http://www.limodev.cn/blog</a> <br />
作者联系方式：李先静 &lt;xianjimli at hotmail dot com&gt;</p>
<p>系统程序员成长计划-文本处理(一)</p>
<p>状态机(4)</p>
<p>XML解析器</p>
<p>XML（Extensible Markup Language）即可扩展标记语言，也是一种常用的数据文件格式。相对于INI来说，它要复杂得多，INI只能保存线性结构的数据，而XML可以保存树形结构的数据。先看下面的例子：</p>
<pre>&lt;?xml version="1.0" encoding="utf-8"?&gt;<br />
&lt;mime-type xmlns="http://www.freedesktop.org/standards/shared-mime-info" type="all/all"&gt;<br />
&lt;!--Created automatically by update-mime-database. DO NOT EDIT!--&gt;<br />
&lt;comment&gt;all files and folders&lt;/comment&gt;<br />
&lt;/mime-type&gt;<br />
</pre>
<p>第一行称为处理指令(PI)，是给解析器用的。这里告诉解析器，当前的XML文件遵循XML 1.0规范，文件内容用UTF-8编码。</p>
<p>第二行是一个起始TAG，TAG的名称为mime-type。它有两个属性，第一个属性的名称为xmlns，值为 http://www.freedesktop.org/standards/shared-mime-info。第二个属性的名称为type，值为 all/all。</p>
<p>第三行是一个注释。</p>
<p>第四行包括一个起始TAG，一段文本和结束TAG。</p>
<p>第五行是一个结束TAG。</p>
<p>XML本身的格式不是本文的重点，我们不详细讨论了。这里的重点是如何用状态机解析格式复杂的数据。</p>
<p>按照前面的方法，先把数据读入到一个缓冲区中，让一个指针指向缓冲区的头部，然后移动指针，直到指向缓冲区的尾部。在这个过程中，指针可能指向：起始TAG，结束TAG，注释，处理指令和文本。由此我们定义出状态机的主要状态：</p>
<p>1. 起始TAG状态<br />
2. 结束TAG状态<br />
3. 注释状态<br />
4. 处理指令状态<br />
5. 文本状态</p>
<p>由于起始TAG、结束TAG、注释和处理指令都在字符&#8216;&lt;&#8217;和&#8216;&gt;&#8217;之间，所以当读入字符&#8216;&lt;&#8217;时，我们还无法知道当前的状态，为了便于处理，我们引入一个中间状态，称为&#8220;小于号之后&#8221;的状态。在读入字符&#8216;&lt;&#8217;和&#8216;!&#8217;之后，还要读入两个&#8216;-&#8217;，才能确定进入注释状态，为了便于处理，再引入两个中间状态&#8220;注释前一&#8221;和&#8220;注释前二&#8221;。再引入一个&#8220;空&#8221;状态，表示不在上述任何状态中。</p>
<p>状态转换函数：<br />
1. 在&#8220;空&#8221;状态下，读入字符&#8216;&lt;&#8217;，进入&#8220;小于号之后&#8221;状态。<br />
2. 在&#8220;空&#8221;状态下，读入非&#8216;&lt;&#8217;非空白的字符，进入&#8220;文本&#8221;状态。<br />
3. 在&#8220;小于号之后&#8221;状态下，读入字符&#8216;！&#8217;，进入&#8220;注释前一&#8221; 状态。<br />
4. 在&#8220;小于号之后&#8221;状态下，读入字符&#8216;?&#8217;，进入&#8220;处理指令&#8221;状态。<br />
5. 在&#8220;小于号之后&#8221;状态下，读入字符&#8216;/&#8217;，进入&#8220;结束TAG&#8221;状态。<br />
6. 在&#8220;小于号之后&#8221;状态下，读入有效的ID字符，进入&#8220;起始TAG&#8221;状态。<br />
7. 在&#8220;注释前一&#8221; 状态下，读入字符&#8216;-&#8217;， 进入&#8220;注释前二&#8221; 状态。<br />
8. 在&#8220;注释前二&#8221; 状态下，读入字符&#8216;-&#8217;， 进入&#8220;注释&#8221; 状态。<br />
9. 在 &#8220;起始TAG&#8221; 状态、&#8220;结束TAG&#8221; 状态 、&#8220;文本&#8221; 状态、&#8220;注释&#8221;状态 和&#8220;处理指令&#8221;状态结束后，重新回到&#8220;空&#8221;状态下。</p>
<p>这个状态机的图形表示如下：<br />
<img alt="" src="http://www.limodev.cn/gallery/albums/blog-pictures/sysprog/xml_state.JPG" /> <br />
下面我们来看看代码实现：</p>
<pre>void xml_parser_parse(XmlParser* thiz, const char* xml)<br />
{<br />
/*定义状态的枚举值*/<br />
enum _State<br />
{<br />
STAT_NONE,<br />
STAT_AFTER_LT,<br />
STAT_START_TAG,<br />
STAT_END_TAG,<br />
STAT_TEXT,<br />
STAT_PRE_COMMENT1,<br />
STAT_PRE_COMMENT2,<br />
STAT_COMMENT,<br />
STAT_PROCESS_INSTRUCTION,<br />
}state = STAT_NONE;<br />
<br />
thiz-&gt;read_ptr = xml;<br />
/*指针从头移动到尾*/<br />
for(; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++)<br />
{<br />
char c = thiz-&gt;read_ptr[0];<br />
<br />
switch(state)<br />
{<br />
case STAT_NONE:<br />
{<br />
if(c == '&lt;')<br />
{<br />
/*在&#8220;空&#8221;状态下，读入字符&#8216;&lt;&#8217;，进入&#8220;小于号之后&#8221;状态。*/<br />
xml_parser_reset_buffer(thiz);<br />
state = STAT_AFTER_LT;<br />
}<br />
else if(!isspace(c))<br />
{<br />
/*在&#8220;空&#8221;状态下，读入非&#8216;&lt;&#8217;非空白的字符，进入&#8220;文本&#8221;状态。*/<br />
state = STAT_TEXT;<br />
}<br />
break;<br />
}<br />
case STAT_AFTER_LT:<br />
{<br />
if(c == '?')<br />
{<br />
/*在&#8220;小于号之后&#8221;状态下，读入字符&#8216;?&#8217;，进入&#8220;处理指令&#8221;状态。*/<br />
state = STAT_PROCESS_INSTRUCTION;<br />
}<br />
else if(c == '/')<br />
{<br />
/*在&#8220;小于号之后&#8221;状态下，读入字符&#8216;/&#8217;，进入&#8220;结束TAG&#8221;状态。*/<br />
state = STAT_END_TAG;<br />
}<br />
else if(c == '!')<br />
{<br />
/*在&#8220;小于号之后&#8221;状态下，读入字符&#8216;！&#8217;，进入&#8220;注释前一&#8221; 状态*/<br />
state = STAT_PRE_COMMENT1;<br />
}<br />
else if(isalpha(c) || c == '_')<br />
{<br />
/*在&#8220;小于号之后&#8221;状态下，读入有效的ID字符，进入&#8220;起始TAG&#8221;状态。*/<br />
state = STAT_START_TAG;<br />
}<br />
else<br />
{<br />
}<br />
break;<br />
}<br />
case STAT_START_TAG:<br />
{<br />
/*进入子状态*/<br />
xml_parser_parse_start_tag(thiz);<br />
state = STAT_NONE;<br />
break;<br />
}<br />
case STAT_END_TAG:<br />
{<br />
/*进入子状态*/<br />
xml_parser_parse_end_tag(thiz);<br />
state = STAT_NONE;<br />
break;<br />
}<br />
case STAT_PROCESS_INSTRUCTION:<br />
{<br />
/*进入子状态*/<br />
xml_parser_parse_pi(thiz);<br />
state = STAT_NONE;<br />
break;<br />
}<br />
case STAT_TEXT:<br />
{<br />
/*进入子状态*/<br />
xml_parser_parse_text(thiz);<br />
state = STAT_NONE;<br />
break;<br />
}<br />
case STAT_PRE_COMMENT1:<br />
{<br />
if(c == '-')<br />
{<br />
/*在&#8220;注释前一&#8221; 状态下，读入字符&#8216;-&#8217;， 进入&#8220;注释前二&#8221; 状态。*/<br />
state = STAT_PRE_COMMENT2;<br />
}<br />
else<br />
{<br />
}<br />
break;<br />
}<br />
case STAT_PRE_COMMENT2:<br />
{<br />
if(c == '-')<br />
{<br />
/*在&#8220;注释前二&#8221; 状态下，读入字符&#8216;-&#8217;， 进入&#8220;注释&#8221; 状态。*/<br />
state = STAT_COMMENT;<br />
}<br />
else<br />
{<br />
}<br />
}<br />
case STAT_COMMENT:<br />
{<br />
/*进入子状态*/<br />
xml_parser_parse_comment(thiz);<br />
state = STAT_NONE;<br />
break;<br />
}<br />
default:break;<br />
}<br />
<br />
if(*thiz-&gt;read_ptr == '\0')<br />
{<br />
break;<br />
}<br />
}<br />
<br />
return;<br />
}<br />
</pre>
<p>解析并没有在此结束，原因是像&#8220;起始TAG&#8221;状态和&#8220;处理指令&#8221;状态等，它们不是原子的，内部还包含一些子状态，如TAG名称，属性名和属性值等，它们需要进一步分解。在考虑子状态时，我们可以忘掉它所处的上下文，只考虑子状态本身，这样问题会得到简化。下面看一下起始TAG的状态机。</p>
<p>假设我们要解析下面这样一个起始TAG：<br />
&lt;mime-type xmlns=&#8221;http://www.freedesktop.org/standards/shared-mime-info&#8221; type=&#8221;all/all&#8221;&gt;</p>
<p>我们应该怎样去做呢？还是按前面的方法，让一个指针指向缓冲区的头部，然后移动指针，直到指向缓冲区的尾部。在这个过程中，指针可能指向，TAG名称，属性名和属性值。由此我们可以定义出状态机的主要状态：</p>
<p>1. &#8220;TAG名称&#8221;状态<br />
2. &#8220;属性名&#8221;状态<br />
3. &#8220;属性值&#8221;状态</p>
<p>为了方便处理，再引两个中间状态，&#8220;属性名之前&#8221;状态和&#8220;属性值之前&#8221;状态。</p>
<p>状态转换函数：</p>
<p>初始状态为&#8220;TAG名称&#8221;状态<br />
1. 在&#8220;TAG名称&#8221;状态下，读入空白字符，进入&#8220;属性名之前&#8221;状态。<br />
2. 在&#8220;TAG名称&#8221;状态下，读入字符&#8216;/&#8217;或&#8216;&gt;&#8217;，进入&#8220;结束&#8221;状态。<br />
3. 在&#8220;属性名之前&#8221;状态下，读入其它非空白字符，进入&#8220;属性名&#8221;状态。<br />
4. 在&#8220;属性名&#8221;状态下，读入字符&#8216;=&#8217;，进入&#8220;属性值之前&#8221;状态。<br />
5. 在&#8220;属性值之前&#8221;状态下，读入字符&#8216;&#8220;&#8217;，进入&#8220;属性值&#8221;状态。<br />
6. 在&#8220;属性值&#8221;状态下，读入字符&#8216;&#8221;&#8217;，成功解析属性名和属性值，回到&#8220;属性名之前&#8221;状态。<br />
7. 在&#8220;属性名之前&#8221;状态下，读入字符&#8216;/&#8217;或&#8216;&gt;&#8217;，进入&#8220;结束&#8221;状态。</p>
<p>由于处理指令(PI)里也包含了属性状态，为了重用属性解析的功能，我们把属性的状态再提取为一个子状态。这样，&#8220;起始TAG&#8221;状态的图形表示如下：<br />
<img alt="" src="http://www.limodev.cn/gallery/albums/blog-pictures/sysprog/xml_tag_state.JPG" /> </p>
<p>下面我们看代码实现：</p>
<pre>static void xml_parser_parse_attrs(XmlParser* thiz, char end_char)<br />
{<br />
int i = 0;<br />
enum _State<br />
{<br />
STAT_PRE_KEY,<br />
STAT_KEY,<br />
STAT_PRE_VALUE,<br />
STAT_VALUE,<br />
STAT_END,<br />
}state = STAT_PRE_KEY;<br />
<br />
char value_end = '\"';<br />
const char* start = thiz-&gt;read_ptr;<br />
<br />
thiz-&gt;attrs_nr = 0;<br />
for(; *thiz-&gt;read_ptr != '\0' &amp;&amp; thiz-&gt;attrs_nr &lt; MAX_ATTR_NR; thiz-&gt;read_ptr++)<br />
{<br />
char c = *thiz-&gt;read_ptr;<br />
<br />
switch(state)<br />
{<br />
case STAT_PRE_KEY:<br />
{<br />
if(c == end_char || c == '&gt;')<br />
{<br />
/*在&#8220;属性名之前&#8221;状态下，读入字符&#8216;/&#8217;或&#8216;&gt;&#8217;，进入&#8220;结束&#8221;状态。*/<br />
state = STAT_END;<br />
}<br />
else if(!isspace(c))<br />
{<br />
/*在&#8220;属性名之前&#8221;状态下，读入其它非空白字符，进入&#8220;属性名&#8221;状态。*/<br />
state = STAT_KEY;<br />
start = thiz-&gt;read_ptr;<br />
}<br />
}<br />
case STAT_KEY:<br />
{<br />
if(c == '=')<br />
{<br />
/*在&#8220;属性名&#8221;状态下，读入字符&#8216;=&#8217;，进入&#8220;属性值之前&#8221;状态。*/<br />
thiz-&gt;attrs[thiz-&gt;attrs_nr++] = (char*)xml_parser_strdup(thiz, start, thiz-&gt;read_ptr - start);<br />
state = STAT_PRE_VALUE;<br />
}<br />
<br />
break;<br />
}<br />
case STAT_PRE_VALUE:<br />
{<br />
/*在&#8220;属性值之前&#8221;状态下，读入字符&#8216;&#8220;&#8217;，进入&#8220;属性值&#8221;状态。*/<br />
if(c == '\"' || c == '\'')<br />
{<br />
state = STAT_VALUE;<br />
value_end = c;<br />
start = thiz-&gt;read_ptr + 1;<br />
}<br />
break;<br />
}<br />
case STAT_VALUE:<br />
{<br />
/*在&#8220;属性值&#8221;状态下，读入字符&#8216;&#8221;&#8217;，成功解析属性名和属性值，回到&#8220;属性名之前&#8221;状态。*/<br />
if(c == value_end)<br />
{<br />
thiz-&gt;attrs[thiz-&gt;attrs_nr++] = (char*)xml_parser_strdup(thiz, start, thiz-&gt;read_ptr - start);<br />
state = STAT_PRE_KEY;<br />
}<br />
}<br />
default:break;<br />
}<br />
<br />
if(state == STAT_END)<br />
{<br />
break;<br />
}<br />
}<br />
<br />
for(i = 0; i &lt; thiz-&gt;attrs_nr; i++)<br />
{<br />
thiz-&gt;attrs[i] = thiz-&gt;buffer + (size_t)(thiz-&gt;attrs[i]);<br />
}<br />
thiz-&gt;attrs[thiz-&gt;attrs_nr] = NULL;<br />
<br />
return;<br />
}<br />
</pre>
<p>记得在XML里，单引号和双引号都可以用来界定属性值，所以上面对此做了特殊处理。</p>
<pre>static void xml_parser_parse_start_tag(XmlParser* thiz)<br />
{<br />
enum _State<br />
{<br />
STAT_NAME,<br />
STAT_ATTR,<br />
STAT_END,<br />
}state = STAT_NAME;<br />
<br />
char* tag_name = NULL;<br />
const char* start = thiz-&gt;read_ptr - 1;<br />
<br />
for(; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++)<br />
{<br />
char c = *thiz-&gt;read_ptr;<br />
<br />
switch(state)<br />
{<br />
case STAT_NAME:<br />
{<br />
/*在&#8220;TAG名称&#8221;状态下，读入空白字符，属性子状态。*/<br />
/*在&#8220;TAG名称&#8221;状态下，读入字符&#8216;/&#8217;或&#8216;&gt;&#8217;，进入&#8220;结束&#8221;状态。*/<br />
if(isspace(c) || c == '&gt;' || c == '/')<br />
{<br />
state = (c != '&gt;' &amp;&amp; c != '/') ? STAT_ATTR : STAT_END;<br />
}<br />
break;<br />
}<br />
case STAT_ATTR:<br />
{<br />
/*进入&#8220;属性&#8221;子状态*/<br />
xml_parser_parse_attrs(thiz, '/');<br />
state = STAT_END;<br />
<br />
break;<br />
}<br />
default:break;<br />
}<br />
<br />
if(state == STAT_END)<br />
{<br />
break;<br />
}<br />
}<br />
<br />
for(; *thiz-&gt;read_ptr != '&gt;' &amp;&amp; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++);<br />
<br />
return;<br />
}<br />
</pre>
<p>处理指令的解析和起始TAG的解析基本上是一样的，这里只是看一下代码：</p>
<pre>static void xml_parser_parse_pi(XmlParser* thiz)<br />
{<br />
enum _State<br />
{<br />
STAT_NAME,<br />
STAT_ATTR,<br />
STAT_END<br />
}state = STAT_NAME;<br />
<br />
char* tag_name = NULL;<br />
const char* start = thiz-&gt;read_ptr;<br />
<br />
for(; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++)<br />
{<br />
char c = *thiz-&gt;read_ptr;<br />
<br />
switch(state)<br />
{<br />
case STAT_NAME:<br />
{<br />
/*在&#8220;TAG名称&#8221;状态下，读入空白字符，属性子状态。*/<br />
/*在&#8220;TAG名称&#8221;状态下，&#8216;&gt;&#8217;，进入&#8220;结束&#8221;状态。*/<br />
if(isspace(c) || c == '&gt;')<br />
{<br />
state = c != '&gt;' ? STAT_ATTR : STAT_END;<br />
}<br />
<br />
break;<br />
}<br />
case STAT_ATTR:<br />
{<br />
/*进入&#8220;属性&#8221;子状态*/<br />
xml_parser_parse_attrs(thiz, '?');<br />
state = STAT_END;<br />
break;<br />
}<br />
default:break;<br />
}<br />
<br />
if(state == STAT_END)<br />
{<br />
break;<br />
}<br />
}<br />
<br />
tag_name = thiz-&gt;buffer + (size_t)tag_name;<br />
<br />
for(; *thiz-&gt;read_ptr != '&gt;' &amp;&amp; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++);<br />
<br />
return;<br />
}<br />
</pre>
<p>注释，结束TAG和文本的解析非常简单，这里结合代码看看就行了：</p>
<p>&#8220;注释&#8221;子状态的处理：</p>
<pre>static void xml_parser_parse_comment(XmlParser* thiz)<br />
{<br />
enum _State<br />
{<br />
STAT_COMMENT,<br />
STAT_MINUS1,<br />
STAT_MINUS2,<br />
}state = STAT_COMMENT;<br />
<br />
const char* start = ++thiz-&gt;read_ptr;<br />
for(; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++)<br />
{<br />
char c = *thiz-&gt;read_ptr;<br />
<br />
switch(state)<br />
{<br />
case STAT_COMMENT:<br />
{<br />
/*在&#8220;注释&#8221;状态下，读入&#8216;-&#8217;，进入&#8220;减号一&#8221;状态。*/<br />
if(c == '-')<br />
{<br />
state = STAT_MINUS1;<br />
}<br />
break;<br />
}<br />
case STAT_MINUS1:<br />
{<br />
if(c == '-')<br />
{<br />
/*在&#8220;减号一&#8221;状态下，读入&#8216;-&#8217;，进入&#8220;减号二&#8221;状态。*/<br />
state = STAT_MINUS2;<br />
}<br />
else<br />
{<br />
state = STAT_COMMENT;<br />
}<br />
break;<br />
}<br />
case STAT_MINUS2:<br />
{<br />
if(c == '&gt;')<br />
{<br />
/*在&#8220;减号二&#8221;状态下，读入&#8216;&gt;&#8217;，结束解析。*/<br />
return;<br />
}<br />
else<br />
{<br />
state = STAT_COMMENT;<br />
}<br />
}<br />
default:break;<br />
}<br />
}<br />
<br />
return;<br />
}<br />
</pre>
<p>&#8220;结束TAG&#8221;子状态的处理：</p>
<pre>static void xml_parser_parse_end_tag(XmlParser* thiz)<br />
{<br />
char* tag_name = NULL;<br />
const char* start = thiz-&gt;read_ptr;<br />
for(; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++)<br />
{<br />
/*读入&#8216;&gt;&#8217;，结束解析。*/<br />
if(*thiz-&gt;read_ptr == '&gt;')<br />
{<br />
break;<br />
}<br />
}<br />
<br />
return;<br />
}<br />
</pre>
<p>&#8220;文本&#8221;子状态的处理：</p>
<pre>static void xml_parser_parse_text(XmlParser* thiz)<br />
{<br />
const char* start = thiz-&gt;read_ptr - 1;<br />
for(; *thiz-&gt;read_ptr != '\0'; thiz-&gt;read_ptr++)<br />
{<br />
char c = *thiz-&gt;read_ptr;<br />
/*读入&#8216;&gt;&#8217;，结束解析。*/<br />
if(c == '&lt;')<br />
{<br />
if(thiz-&gt;read_ptr &gt; start)<br />
{<br />
}<br />
thiz-&gt;read_ptr--;<br />
return;<br />
}<br />
else if(c == '&amp;')<br />
{<br />
/*读入&#8216;&amp;&#8217;，进入实体(entity)解析子状态。*/<br />
xml_parser_parse_entity(thiz);<br />
}<br />
}<br />
<br />
return;<br />
}<br />
</pre>
<p>实体(entity)子状态比较简单，这里不做进一步分析了，留给读者做练习吧</p>
<img src ="http://www.blogjava.net/shiliqiang/aggbug/286306.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shiliqiang/" target="_blank">石头@</a> 2009-07-10 18:57 <a href="http://www.blogjava.net/shiliqiang/articles/286306.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>tomcat 在eclipse中的部署</title><link>http://www.blogjava.net/shiliqiang/articles/282894.html</link><dc:creator>石头@</dc:creator><author>石头@</author><pubDate>Wed, 17 Jun 2009 10:07:00 GMT</pubDate><guid>http://www.blogjava.net/shiliqiang/articles/282894.html</guid><wfw:comment>http://www.blogjava.net/shiliqiang/comments/282894.html</wfw:comment><comments>http://www.blogjava.net/shiliqiang/articles/282894.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shiliqiang/comments/commentRss/282894.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shiliqiang/services/trackbacks/282894.html</trackback:ping><description><![CDATA[<h3 class="" title=""><a href="http://gui1401.javaeye.com/blog/194979">Tomcat源码学习（一）</a></h3>
<p><span style="font-size: small;"><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank">转自:http://carllgc.blog.ccidnet.com/blog-htm-do-showone-uid-4092-type-blog-itemid-263093.html</a></span></p>
<p><span style="font-size: small;"><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank">作为一位<span>Java</span><span>程序员，如果您没有接触过开源软件、项目或框架的话，恐怕有些不可思议。轰轰烈烈的开源运动起源于</span><span>Linux</span><span>操作系统，</span><span>Apache</span><span>基金会在其中扮演了中流砥柱的角色，业界巨擘</span><span>SUN</span><span>，</span><span>IBM</span><span>，</span><span> </span><span>BEA </span><span>和</span><span>Oracle</span><span>等公司的积极参与，使得声势浩大的开源运动成为软件开发领域势不可挡的力量。</span><span>2001</span><span>年</span><span>11</span><span>月，</span><span>IBM</span><span>向</span><span>Apache</span><span>基金会捐献出</span><span>Visual Age for Java</span><span>，这个看似穷途末路的产品经众多高手的改造，演变为辉煌一时的</span><span>Eclipse</span><span>，直接击败了不开源的</span><span>JBuilder</span><span>，让做编译器起家的</span><span>Borland</span><span>公司几乎关张大吉。</span><span>Eclipse</span><span>这个产品如此经典，以至于微软的</span><span>Visual Studio</span><span>都得向它学习。在</span><span>Apache Harmony</span><span>的围追堵截下，</span><span>Java</span><span>的发明者</span><span>Sun</span><span>公司一看势头不妙，于</span><span>2006</span><span>年宣布</span><span>Java</span><span>开源，随后又公开了其旗舰级产品</span><span>Solaris</span><span>的源代码。今年</span><span>1</span><span>月，开源的死对头、冷酷自私的微软也不得不在</span><span>MS-RL</span><span>协议下公开</span><span>.Net</span><span>的源代码。但是，在这如火如荼的开源运动中，我们中国的程序员又有多少贡献呢，我们开创了哪些框架、项目和产品，为开源界添砖加瓦呢？以笔者短浅的目光看来，我们对开源界贡献的东西恐怕很少，能够与国外经典开源项目一较高下的，少之又少矣！</span></a></span><span style="font-size: small;"><span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"> <br />
</a></span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"><br />
<span>作为一名中国的程序员，咱们能没有遗憾吗？为什么经典的</span><span>Apache Web Server</span><span>不是中国人写的；为什么</span><span>Linus Torvalds</span><span>在大学时代就写出</span><span>Linux</span><span>并振臂一呼，应者云集；为什么</span><span>JBoss</span><span>能与巨无霸式的</span><span>Websphere</span><span>相抗衡；为什么</span><span>MySQL</span><span>能在</span><span>Oracle</span><span>和</span><span>SQL Server</span><span>的夹击下发展并壮大</span><span>&#8230;&#8230; </span><span>？如此等等问题，在遗憾之余，我想我们应该花点时间好好思考一下，中国的软件产业怎么了，中国的程序员又怎么啦？</span></a></span><span style="font-size: small;"><span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"> <br />
</a></span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"><br />
<span>在笔者看来，我们的程序员对开源的理解是相当狭隘的。国学大师王国维曾说过，古往今来成大学问大事业者要经历三种境界，&#8220;</span><span>昨夜西风凋碧树，独上高楼，望尽天涯路</span><span>&#8221;，这是第一重境界，迷惘也；&#8220;</span><span>衣带渐宽终不悔，为伊消得人憔悴&#8221;，苦苦求索之境界也；第三重境界为&#8220;众里寻他千百度，蓦然回首，那人却在灯火阑珊处&#8221;，经历多少次的失败和挫折后，终于参透真谛，领悟真理。我觉得开源也有三重境界：</span></a></span><span style="font-size: small;"><span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"> <br />
</a></span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"><br />
<span>首先，我们要敞开心胸，拥抱开源（</span></a></span><span style="font-size: small;"><span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank">Open
to Open
Source)。这重境界我们大家都能做到，拿来主义嘛，谁人不会。当我们的项目需要数据库时，就去下载一个免费MySQL；需要IDE时，去下载
Eclipse；需要版本控制工具时，就去下载CVS；需要写搜索引擎时，Lucene可能是我们的最爱；当我们开发J2EE
Web应用时，Struts/JSF加Hibernate/iBATIS再加上Spring或许成为我们的首选架构。但是，我们绝大部分程序员都停留在这
个层次上，大家下载之后，看看文档介绍，安装、配置并能运行，就以为万事大吉，一切顺利。偶尔遇到一些问题，去Google一搜，答案立马可得。 <br />
</a></span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"><br />
<span>其次，我们要深入开源，了解开源（</span></a></span><span style="font-size: small;"><span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank">Dig
into Open
Source)。要达到这个层次，就有些难度了。我们不但要知其然，还要知其所以然。&#8220;知其所以然&#8221;的最好办法就是下载源代码，仔细研读，揣摩并领会源代
码的精义，看看这些经过诸多高手修改的源代码究竟藏有什么玄机，我们能从其中学习到哪些设计思想及设计模式，能复用其中哪些源代码，人家运用了哪些软件管
理思想把这些来自世界各地程序员的劳动汇集成一个产品，代码架构如何，软件配置管理又是怎样进行的&#8230;&#8230;，等等等等，我们从源代码中学习的东西太多了。在阅
读源代码时，我们要多问自己几个为什么，这样就会收获更多。 <br />
</a></span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"><br />
<span>再次，我们要融入开源，贡献开源（</span></a></span><span style="font-size: small;"><span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank">Get
involved in Open
Source)。当我们彻底理解该项目源代码后，我们应发挥一下&#8220;人人为我，我为人人&#8221;的思想，或结合您的实际需要，或结合您的新想法，或针对Mail
lists上的问题，对该开源项目加以改进和创新，并把自己的代码贡献出来，让大家评估。当然，如果您有好的想法，您完全可以创建自己的开源项
目，Apache基金会中众多的开源项目不都是我们广大程序员一手创建的吗？但是，在创建新开源项目时，切忌不要重新发明轮子。 <br />
</a></span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"><br />
<span>笔者才疏学浅，想以</span></a></span><span style="font-size: small;"><span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank">Apache Jakarta项目包中的核心项目Tomcat为例，希望通过阅读源码，能从这个经典项目中学到更多的东西，为我们中国的开源事业起到抛砖引玉的作用。 <br />
</a></span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"><br />
<span>下面我们就开始我们的</span></a></span><span style="font-size: small;"><span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank">Tomcat源码学习之旅。 <br />
</a></span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"><br />
<span>1. </span><span>下载</span></a></span><span style="font-size: small;"><span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank">Tomcat6.0的源代码 <br />
</a></span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"><br />
<span>首先，我们得下载</span><span>Tomcat6.0的源代码。Tomcat源代码的版本控制工具不是CVS，而是Subversion，如果您的机器上没有安装Subversion，请从</span><span> </span></a></span><a href="http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=91" target="_blank"><span><span style="color: #800080;"><span style="font-size: small;">http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=91</span></span></span></a><span style="font-size: small;"><span> </span><span>下载并安装这个开源的版本控制工具。当然，如果您想从</span><span>Eclipse</span><span>中直接导入</span><span>Tomcat</span><span>源代码，请从</span></span><a href="http://subclipse.tigris.org/update_1.0.x" target="_blank"><span><span style="font-size: small; color: #3d7db3;">http://subclipse.tigris.org/update_1.0.x</span></span></a><span style="font-size: small;"><span>下载</span><span>Subversion</span><span>插件，即可导入</span><span>Tomcat</span><span>源代码。安装完成后，请在</span><span>MS-DOS</span><span>窗口中键入</span><span>svn export help</span><span>，您将会看到：</span></span><span><span style="font-size: small;"> <br />
</span></span><br />
<span>C:\Documents and Settings\carlwu&gt;svn help export <br />
</span><br />
<span>export: </span><span>产生一个无版本控制的目录树副本。</span><span> <br />
</span><br />
<span>用法</span><span>: 1</span><span>、</span><span>export [-r REV] URL[@PEGREV] [PATH] <br />
</span><br />
<span>2</span><span>、</span><span>export [-r REV] PATH1[@PEGREV] [PATH2] <br />
</span><br />
<span>1</span><span>、从</span><span> URL </span><span>指定的仓库，导出一个干净的目录树到</span><span> PATH</span><span>。如果有指定</span><span> <br />
</span><br />
<span>REV </span><span>的话，内容即为该版本的，否则就是</span><span> HEAD </span><span>版本。如果</span><span> PATH <br />
</span><br />
<span>被省略的话，</span><span>URL</span><span>的最后部份会被用来当成本地的目录名称。</span><span> <br />
</span><br />
<span>2</span><span>、在工作副本中，从指定的</span><span> PATH1 </span><span>导出一个干净的目录树到</span><span> PATH2</span><span>。如果</span><span> <br />
</span><br />
<span>有指定</span><span> REV </span><span>的话，会从指定的版本导出，否则从工作副本导出。如果</span><span> <br />
</span><br />
<span>PATH2 </span><span>被省略的话，</span><span>PATH1 </span><span>的最后部份会被用来当成本地的目录名称。</span><span> <br />
</span><br />
<span>如果没有指定</span><span> REV </span><span>的话，所有的本地修改都保留，但是未纳入版本控制</span><span> <br />
</span><br />
<span>的文件不会被复制。</span><span> <br />
</span><br />
<br />
<br />
<span>如果指定了</span><span> PEGREV </span><span>，将从指定的版本本开始查找。</span><span> <br />
</span><br />
<span>有效选项</span><span>:</span><span>。。。。。。</span><span> <br />
</span><br />
<span style="font-size: small;"><span>我们看到</span><span>Subversion</span><span>给我们提供了非常友好的帮助，并且是中文的，看来中国程序员对这个开源项目有所贡献。接下来，请在</span><span>MS-DOS</span><span>下键入：</span><span> <br />
</span></span><br />
<span>svn export </span><a href="http://svn.apache.org/repos/asf/tomcat/tc6.0.x/tags/TOMCAT_6_0_0/" target="_blank"><span><span style="color: #800080;">http://svn.apache.org/repos/asf/tomcat/tc6.0.x/tags/TOMCAT_6_0_0/</span></span></a><span> D:\carl_wu\tomcat\src\ <br />
</span><br />
<span style="font-size: small;"><span>这个命令的意思是把</span><span>Tomcat6.0</span><span>的源代码从</span><span>Subversion</span><span>库中导入到本机的</span><span>D:\carl_wu\tomcat\src\</span><span>目录，命令运行后，您稍等几分钟，就会看到</span><span>Tomcat</span><span>的源代码顺利导入到目标目录。下面是源代码的目录机构，从这个目录结构中，我们可以看出该项目的开发者使用的</span><span>IDE</span><span>是</span><span>Eclipse</span><span>，因为我们看到了熟悉的</span><span>.project</span><span>及</span><span>.classpath</span><span>文件。如果您打算开发一个</span><span>Stand alone</span><span>的</span><span>Java</span><span>应用程序，不妨借鉴一下</span><span>Tomcat</span><span>的目录结构，把脚本文件放在</span><span>bin</span><span>目录，将</span><span>xml</span><span>和</span><span>properties</span><span>配置文件放在</span><span>conf</span><span>目录中，把</span><span>Java</span><span>源码文件放在</span><span>java</span><span>或者</span><span>src</span><span>目录中，资源文件比如说图片文件，</span><span>ini</span><span>文件及其它的一些静态资源文件可以放在</span><span>res</span><span>目录，测试源代码可以放在</span><span>test</span><span>目录中。这是一个典型的</span><span>Java</span><span>应用程序的目录机构，笔者以前曾接触到一个来自美国的产品，其源代码目录结构和</span><span>Tomcat</span><span>及其相像。</span></span> <br />
<span style="font-size: small;"><strong><br />
<img src="http://carllgc.blog.ccidnet.com/attachment/4092_610d1d7f5769fe0.jpg" alt="" border="0" /><br />
<br />
</strong></span><strong><br />
<span><br />
</span><br />
<span><span style="font-size: small;">2.</span></span> <span style="font-size: small;"><span>编译并运行</span><span> <br />
</span></span><br />
<span style="font-size: small;"><span>代码下载后，我们接下来就是要编译并运行</span><span>Tomcat</span><span>。一提编译，我们不禁会想到可爱的</span><span>Ant</span><span>。不错，</span><span>Tomcat</span><span>正是以</span><span>Ant</span><span>作为编译工具，如果您还没有安装，请从</span></span><a href="http://ant.apache.org/bindownload.cgi" target="_blank"><span><span style="color: #800080;"><span style="font-size: small;">http://ant.apache.org/bindownload.cgi</span></span></span></a><span style="font-size: small;"><span> </span><span>处下载并安装它。然后，请从</span><span>Tomcat</span><span>的源代码文件找到</span><span>build.properties.default</span><span>文件，并将该文件复制到</span><span>build.properties</span><span>，然后打开</span><span>build.properties</span><span>，找到下面这行：</span><span> <br />
</span></span><br />
<span><span style="font-size: small;">base.path=/usr/share/java <br />
</span></span><br />
<span style="font-size: small;"><span>将它改为：</span><span> <br />
</span></span><br />
<span style="font-size: small;"><span>base.path=</span> <span>D:/carl_wu/tomcat/share <br />
</span></span><br />
<span style="font-size: small;"><span>在</span><span>Tomcat</span><span>编译过程中，</span><span>Ant</span><span>会让我们下载一些必要的依赖项目，</span><span>base.path</span><span>目录就是用来保存这些项目文件的，我们可以将这个属性指向一个已经存在的目录。修改完</span><span>base.path</span><span>后，我们回到</span><span>MS-DOS</span><span>窗口，切换到</span><span>Tomcat</span><span>源代码所在目录，然后运行</span><span>ant download</span><span>命令，如下图所示：</span></span><span> <br />
</span><br />
<strong><br />
<img src="http://carllgc.blog.ccidnet.com/attachment/4092_e3a99800a09b667.jpg" alt="" border="0" /><br />
<br />
<br />
<span style="font-size: small;"><span>一分钟未到，</span><span>Ant</span><span>就告诉我们一个错误并提示我们编译失败，具体错误信息如下：</span><span> <br />
</span></span><br />
<span>downloadzip: <br />
</span><br />
<span>[get] Getting: <a href="http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip" target="_blank"><span style="color: #3d7db3;">http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip</span></a></span> <br />
<span>[get] To: D:\carl_wu\tomcat\share\file.zip </span><br />
<span>[get] Error opening connection java.io.FileNotFoundException: <a href="http://sunsite.informatik.rwth-aachen.de:3080/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip" target="_blank"><span style="color: #3d7db3;">http://sunsite.informatik.rwth-aachen.de:3080/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip</span></a> <br />
</span><br />
<span>[get] Error opening connection java.io.FileNotFoundException: <a href="http://sunsite.informatik.rwth-aachen.de:3080/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip" target="_blank"><span style="color: #3d7db3;">http://sunsite.informatik.rwth-aachen.de:3080/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip</span></a> <br />
</span><br />
<span>[get] Error opening connection java.io.FileNotFoundException: <a href="http://sunsite.informatik.rwth-aachen.de:3080/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip" target="_blank"><span style="color: #3d7db3;">http://sunsite.informatik.rwth-aachen.de:3080/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip</span></a> <br />
</span><br />
<span>[get] Can't get <a href="http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip" target="_blank"><span style="color: #3d7db3;">http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip</span></a> to D:\carl_wu\tomcat\share\file.zip <br />
</span><br />
<span>BUILD FAILED <br />
</span><br />
<span>D:\carl_wu\tomcat\src\build.xml:554: The following error occurred while executing this line: <br />
</span><br />
<span>D:\carl_wu\tomcat\src\build.xml:514: Can't get <a href="http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip" target="_blank"><span style="color: #3d7db3;">http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip</span></a> to D:\carl_wu\tomcat\share\file.zip <br />
</span><br />
<span>Total time: 41 seconds <br />
</span><br />
<span><span style="font-size: small;">这个编译错误非常简单，就是找不到</span></span><span><a href="http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip" target="_blank">http://sunsite.informatik.rwth-aachen.de/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip</a></span><span style="font-family: calibri;"> </span><span>文件。有人可能会想，</span><span>Tomcat</span><span>的编译和</span><span>Eclipse</span><span>的</span><span>JDT</span><span>有什么关系？其实不然，</span><span>Tomcat</span><span>是在</span><span>Eclipse</span><span>下开发的，所以需要</span><span>Eclipse</span><span>的</span><span>JDT</span><span>（</span><span>Java Development tooling</span><span>）插件来编译</span><span>Tomat</span><span>源代码。既然找不到，我们只好自己动手，上</span><span>Google</span><span>一搜，马上发现这个文件的有效下载地址为：</span><span style="font-size: small;"><span>http://mirror.calvin.edu/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip</span><span>。我们</span><span>打开</span><span>刚才的</span><span>build.properties</span></span><span>文件，将其</span><span>34</span><span>行修改为：</span><span> <br />
</span><br />
<span style="font-family: calibri;">jdt.loc=</span> <a href="http://mirror.calvin.edu/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zip" target="_blank"><span style="color: #3d7db3;">http://mirror.calvin.edu/eclipse/downloads/drops/R-3.2-200606291905/eclipse-JDT-3.2.zi<span>p</span></span></a><br />
<br />
<span style="font-size: small;"><span>修改保存</span><span>build.properties</span><span>文件后，重新开始</span><span>ant download</span><span>任务。这次我们等的时间较长，因为</span><span>eclipse-JDT-3.2.zip</span><span>大约有</span><span>19M</span><span>，下载需要一段时间。我们可乘此机会去泡杯茶弄点咖啡什么的，等我们品茶回来，发现敬业的蚂蚁</span><span>Ant</span><span>告诉我们编译成功，虽然编译器给出几个警告。这时我们可发现刚才创建的</span><span>base.path</span><span>目录（</span><span>D:\carl_wu\tomcat\share</span><span>）中已经下载了</span><span>6</span><span>个依赖项目，它们都是</span><span>Tomcat</span><span>编译所必须的。</span><span> <br />
</span></span><br />
<span style="font-size: small;"><span>下面就开始真正的编译任务了，请在</span><span>MS-DOS</span><span>窗口内键入</span><span>ant</span><span>并回车，</span><span>Ant</span><span>将在</span><span>2</span><span>分钟内编译</span><span>1000</span><span>多个源文件并将</span><span>Tomcat</span><span>部署到</span><span>output</span><span>目录。编译顺利完成后，请打开</span><span>Tomcat</span><span>的源代码目录，会发现多了一个</span><span>output</span><span>目录，这是</span><span>Ant</span><span>的编译后的输出目录。请打开</span><span>Tomcat</span><span>源代码的</span><span>output\build\bin</span><span>子目录，双击</span><span>startup.bat</span><span>文件，我们即可成功启动</span><span>Tomcat6.0</span><span>，此时我们的编译工作就算顺利完成了。</span></span> <br />
<span style="font-size: small;"><span><br />
</span></span><br />
<span style="font-size: small;"><span>3. 导入源代码到</span><span>Eclipse <br />
</span></span><br />
<span style="font-size: small;"><span>3.1 </span><span>请打开</span><span>Eclipse</span><span>，新建一个</span><span>Java</span><span>项目，然后点击&#8220;</span><span>Next</span><span>&#8221;按钮，请选择&#8220;</span><span>Create project from existing source</span><span>&#8221;，</span><span> </span><span>并在</span><span>Directory</span><span>文本框内填入我们刚才下载的</span><span>Tomcat</span><span>源代码目录（</span><span>i.e. D:\carl_wu\tomcat\src)</span><span>，然后点击&#8220;</span><span>Next</span><span>&#8221;直至结束。</span></span><span> <br />
</span><br />
<strong><br />
<img src="http://carllgc.blog.ccidnet.com/attachment/4092_4de67a9f97a5884.jpg" alt="" border="0" /><br />
<br />
<span style="font-size: small;"><span>3.2 </span><span>我们将会看到</span><span>Eclipse</span><span>拒绝编译，这是因为</span><span>Eclipse</span><span>找不到该项目指定的库文件。请右击该项目，在弹出菜单中选择&#8220;</span><span>Properties</span><span>&#8221;</span><span>&#224;</span><span>&#8220;</span><span>Libraries</span><span>&#8221;，然后删除两个以</span><span>TOMCAT_LIBS</span><span>开头的两个库文件，只保留一个</span><span>JRE</span><span>库文件，然后点击&#8220;</span><span>OK</span><span>&#8221;按钮，这时</span><span>Eclipse</span><span>开始编译</span><span>Tomcat</span><span>源代码，但是发现一堆错误，这是因为我们没有为该项目添加编译所必须的</span><span>Jar</span><span>包。</span><span> <br />
</span></span><br />
<span style="font-size: small;"><span>3.3 </span><span>准备好</span><span>Tomcat</span><span>项目所必须的</span><span>jar</span><span>文件，其实，刚才我们运行</span><span>ant download</span><span>任务时，已经下载过这些</span><span>jar</span><span>文件包。</span><span> <br />
</span></span><br />
<span>ant.jar </span><span>（请在</span><span>ant</span><span>安装目录的</span><span>lib</span><span>子目录中拷贝）</span><span> <br />
</span><br />
<span>commons-collections-3.1.jar </span><span>（从刚才</span><span>Ant</span><span>下载的</span><span>commons-collections-3.1</span><span>子目录中拷贝）</span><span> <br />
</span><br />
<span>commons-dbcp-1.2.1.jar</span><span>（从刚才</span><span>Ant</span><span>下载的</span><span>commons-dbcp-1.2.1</span><span>子目录中拷贝）</span><span> <br />
</span><br />
<span>commons-logging-1.1.jar</span><span>（如果您本机没有这个</span><span>jar</span><span>包，请从</span><span><font><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"><span style="color: #800080;">http://commons.apache.org/downloads/download_logging.cgi</span></a></font></span><a href="http://commons.apache.org/downloads/download_logging.cgi" target="_blank"><span>处下载）</span><span> <br />
</span><br />
<span>commons-pool-1.2.jar</span><span>（从刚才</span><span>Ant</span><span>下载的</span><span>commons-pool-1.2</span><span>子目录中拷贝）</span><span> <br />
</span><br />
<span>org.eclipse.jdt.core_3.2.0.v_671.jar</span><span>（从刚才</span><span>Ant</span><span>下载的</span><span>eclipse\plugins</span><span>子目录中拷贝）</span><span> <br />
</span><br />
<span style="font-size: small;"><span>3.4 </span><span>当我们准备好这些</span><span>jar</span><span>文件后，将这些文件拷贝到某一目录（比如说</span><span>D:\carl_wu\tomcat\tomcat_lib</span><span>目录），然后在</span><span>Eclipse</span><span>中新建一个</span><span>User Libraries</span><span>，我们将这个新建的</span><span>User Libraries</span><span>命名为</span><span>TOMCAT_LIBS</span><span>，并把这些文件加到</span><span>TOMCAT_LIBS</span><span>。然后将我们新建的</span><span>TOMCAT_LIBS</span><span>添加到</span><span>Tomcat6</span><span>项目。另外，别忘了把</span><span>JUnit</span><span>库也加到</span><span>Tomcat6</span><span>项目。这时</span><span>Eclipse</span><span>开始重新编译，编译过程顺利通过，所有错误均消失，此时</span><span>Tomcat6</span><span>项目的目录结构如下：</span></span><span> <br />
</span><br />
<strong><br />
<img src="http://carllgc.blog.ccidnet.com/attachment/4092_08875f12edbbd58.jpg" alt="" border="0" /><br />
<br />
<br />
<span style="font-size: small;"><span>还有，请把</span><span>test</span><span>目录也加入到源代码中，方法是在</span><span>Eclipse</span><span>中右击</span><span>&#8221;test&#8221;</span><span>目录，然后在弹出菜单中选择&#8220;</span><span>Build path&#8221;</span><span>&#224;</span><span>&#8221;Use as Source Folder</span><span>&#8221;，之后我们会看到</span><span>test</span><span>目录上就多了个源代码的符号，如上图所示。</span><span> <br />
</span></span><br />
<span style="font-size: small;"><span>3.5</span><span>在</span><span>Eclipse</span><span>中运行</span><span>Tomcat</span><span>。请找到</span><span>Tomcat</span><span>的启动主类</span><span>org.apache.catalina.startup.Bootstrap</span><span>，右击这个类，在弹出菜单中选择&#8220;</span><span>Run As&#8230;&#8221;</span><span>&#224;</span><span>&#8221;Open Run Dialog&#8230;&#8221;</span><span>，然后在弹出的&#8220;</span><span>Run</span><span>&#8221;窗口中填入程序运行参数&#8220;</span><span>start&#8221;</span><span>和</span><span>JVM</span><span>运行参数</span><span>catalina.home</span><span>，如下面窗口所示：</span></span><span> <br />
</span><br />
<strong><br />
<img src="http://carllgc.blog.ccidnet.com/attachment/4092_80cf1e82c64d6c2.jpg" alt="" border="0" /><br />
<br />
<span style="font-size: small;"><span>然后点击&#8220;</span><span>Run</span><span>&#8221;按钮，我们将会看到</span><span>Tomcat</span><span>正常启动。恭喜，咱们的</span><span>Tomcat</span><span>源码已经成功导入</span><span>Eclipse</span><span>，这时，可视化的</span><span>UML</span><span>分析工具及</span><span>Debug</span><span>工具就能派上用场了。</span><span> <br />
</span></span><br />
<span style="font-size: small;"><span>3.5 </span><span>调试</span><span>Tomcat</span><span>，请打开</span><span>org.apache.jasper.compiler.Compiler</span><span>类的源代码，在</span><span>generateJava()</span><span>方法的第一行打一个断点，然后在</span><span>Eclipse</span><span>的调试状态下运行</span><span>Tomcat</span><span>，等</span><span>Tomcat</span><span>运行后，打开我们的浏览器，在地址栏中输入</span></span></strong></strong></a><strong><strong><font><a href="http://localhost:8080/examples/jsp/jsp2/el/basic-comparisons.jsp" target="_blank"><span style="color: #3d7db3;">http://localhost:8080/examples/jsp/jsp2/el/basic-comparisons.jsp</span></a></font><a href="http://localhost:8080/examples/jsp/jsp2/el/basic-comparisons.jsp" target="_blank"><span>并回车，然后我们可观察到</span><span>Eclipse</span><span>此时切换至调试视图：</span><span> <br />
</span><br />
<span><span style="font-size: small;"><strong><br />
<img src="http://carllgc.blog.ccidnet.com/attachment/4092_69c151698239193.jpg" alt="" border="0" /><br />
</strong></span></span><strong><br />
<br />
<span><br />
<br />
<span>上面的小实验表明我们可以在</span><span>Eclipse</span><span>中通过</span><span>Debugger</span><span>观察</span><span>Tomcat</span><span>的内部运行机理。另外补充一点，上面的</span><span>generateJava</span><span>方法是将</span><span>jsp</span><span>动态编译至</span><span>java class</span><span>，这个方法只是在第一次请求或者</span><span>Jsp</span><span>源码发生变化时执行，如果您再次在浏览器中发送同样的请求，您将看不到上图的</span><span>Debug</span><span>界面，因为该方法不再执行。</span><span> <br />
</span></span><br />
<span style="font-size: small;"><span>另外，还有一点很有意思。</span><span>Tomcat6</span><span>以前版本的源代码分散在好几个子项目中，他们分别叫做</span><span>jakarta-servletapi-5</span><span>，</span><span>jakarta-tomcat-5</span><span>，</span><span>jakarta-tomcat-catalina</span><span>，</span><span>jakarta-tomcat-connectors</span><span>和</span><span>jakarta-tomcat-jasper</span><span>，我觉得</span><span>Tomcat</span><span>的开发者可能嫌这样做太麻烦了，所以</span><span>Tomcat6</span><span>版本中将这些子项目都合并在一起了。但是，这种做法不利于我们阅读理解源代码</span></span></strong></a></strong></strong></strong></strong></strong></p>
<img src ="http://www.blogjava.net/shiliqiang/aggbug/282894.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shiliqiang/" target="_blank">石头@</a> 2009-06-17 18:07 <a href="http://www.blogjava.net/shiliqiang/articles/282894.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>