测试与质量的关系 测试有助于提高软件的质量,但是提高软件的质量不能依赖于测试。测试与质量的关系很象在考试中“检查”与“成绩”的关系。 学习好的学生,在考试时通过认真检查能减少因疏忽而造成的答题错误,从而“提高”了考试成绩(取得他本来就该得的好成绩)。 而学习差的学生,他原本就不会做题目,无论检查多么细心,也不能提高成绩。 所以说,软件的高质量是设计出来的,而不是靠测试修补出来的。 I love U software testing

发布新日志

  • 开源的数据库连接池 SQL Relay 介绍

    2007-08-07 18:08:07

    概述:
    SQL Relay是个功能强大并且非常容易使用的持久数据库连接池系统,能够运行在Unix/Linux系统下,能够支持大部分主流的数据库系统和大部分的编程语言,有效的减轻服务器的负载和移植问题,是很多Web应用数据库连接池的一个选择。


    SQL Relay 是适合于Unix/Linux下的一个持久数据库连接池,代理服务器和负载平衡系统。

     

    SQL Relay的特点

    • 快速的执行数据库驱动的Web应用
    • 增强的可测性数据库驱动的Web应用
    • 分配数据库的访问连接
    • 关闭数据库的访问连接
    • 从不支持的平台访问数据库
    • 从一个数据库到另一种数据库移植应用


    SQL Relay支持的数据库系统:

    • Oracle
    • MySQL
    • mSQL
    • PostgreSQL
    • Sybase
    • MS SQL Server
    • IBM DB2
    • Interbase
    • Sybase
    • SQLite
    • ODBC
    • MS Access


    SQL Relay 客户端API支持一些高级的数据库操作,例如绑定变量、多记录获取、客户端结果的缓存和支持事务等。

    SQL Relay 的客户端API支持的编程语言有

    • C
    • C++
    • Perl
    • Python
    • PHP
    • Ruby
    • Java
    • TCL
    • Zope


     
    SQL Relay 给下面的数据库抽象层提供驱动支持:

    • Perl DBD
    • Python DB
    • Ruby DBD
    • PHP Pear DB


    SQL Relay 的支持:

    • 命令行客户端
    • 一个GUI的配置工具
    • 丰富的文档


    [  相关链接 ]

  • Linux 配置无线网络常用命令

    2007-08-07 18:06:47

    Connecting to an Open or WEP enabled WLAN (DHCP)

     

    Connecting to an Open or WEP enabled WLAN (Manual IP Setup)

     

    iwconfig commands

     

    ifconfig commands

     

    iwpriv commands

     

    iwlist commands

     

    madwifi-ng commands

     

    NOTE: NOT ALL CARDS/FIRMWARE SUPPORT ALL OF THE COMMANDS LISTED BELOW.

     

     

    Note: To connect your Linux machine to a WLAN using WPA, WPA2 or 802.1X you will need to use WPA Supplicant

     

     

    Connecting to an OPEN / WEP WLAN (DHCP)

     

    Note: replace [interface] with your interface name as required (e.g. eth1, wlan0, ath0 etc.)

     

    iwconfig [interface] mode managed key [WEP key] (128 bit WEP use 26 hex characters, 64 bit WEP uses 10)

     

     

    iwconfig essid "[ESSID]" (Specify ESSID for the WLAN)

     

     

    dhclient [interface] (to receive an IP address, netmask, DNS server and default gateway from the Access Point)

     

     

    ping www.bbc.co.uk  (if you receive a reply you have access)

     

     

    Connecting to an OPEN / WEP WLAN (Manual IP Setup)

     

    Note: replace [interface] with your interface name as required (e.g. eth1, wlan0, ath0 etc.)

     

    It may be necessary to run some packet capture software (e.g. Ethereal) to determine the IP addresses of both the Default Gateway and DNS servers.

     

    iwconfig [interface] mode managed key [WEP key] (128 bit WEP use 26 hex characters, 64 bit WEP uses 10)

     

     

    iwconfig essid "[ESSID]"

     

     

    ifconfig [interface] [IP address] netmask [subnetmask]

     

     

    route add default gw [IP of default gateway] (Configure your default gateway; usually the IP of the Access Point)

     

     

    echo nameserver [IP address of DNS server]  >>  /etc/resolve.conf (Configure your DNS server)

     

     

    ping www.bbc.co.uk (if you receive a reply you have access)

     

     

     

    iwconfig Commands:

     

    Note: replace [interface] with your interface name as required (e.g. eth1, wlan0, ath0 etc.)

     

    iwconfig [interface] mode master (set the card to act as an access point mode)

     

     

    iwconfig [interface] mode managed (set card to client mode on a network with an access point)

     

     

    iwconfig [interface] mode ad-hoc (set card to peer to peer networking or no access point mode)

     

     

    iwconfig [interface] mode monitor (set card to RFMON mode our favourite)

     

     

    iwconfig [interface] essid any (with some cards you may  disable the  ESSID  checking)

     

     

    iwconfig [interface] essid ?/span>your ssid_here?(configure ESSID for network)

     

     

    iwconfig [interface] key 1111-1111-1111-1111 (set 128 bit WEP key)

     

     

    iwconfig [interface] key 11111111 (set 64 bit WEP key)

     

     

    iwconfig [interface] s:mykey (set key as an ASCII string)

     

     

    iwconfig [interface] key off (disable WEP key)

     

     

    iwconfig [interface] key open (sets open mode, no authentication is used and card may accept non-encrypted sessions)

     

     

    iwconfig [interface] channel [channel no.] (set a channel 1-14)

     

     

    iwconfig [interface] channel auto (automatic channel selection)

     

     

    iwconfig [interface] freq 2.422G (channels can also be specified in GHz)

     

     

    iwconfig [interface] ap 11:11:11:11:11:11 (Force card to register AP address)

     

     

    iwconfig [interface] rate 11M (card will use the rate specified)

     

     

    iwconfig [interface] rate auto (select automatic rate)

     

     

    iwconfig [interface] rate auto 5.5M (card will use the rate specified and any rate below as required)

     

     

    ifconfig Commands:

     

    Note: replace [interface] with your interface name as required (e.g. eth1, wlan0, ath0 etc.)

     

    ifconfig [interface] up (bring up specified interface)

     

     

    ifconfig [interface] down (take down specified interface)

     

     

    ifconfig [interface] [IP address] netmask [subnet-mask] (manually set IP and subnet-mask details)

     

     

    ifconfig [interface] hw ether [MAC] (Change the wireless cards MAC address, specify in format 11:11:11:11:11:11)

     

     

    iwpriv Commands:

     

    Note: replace [interface] with your interface name as required (e.g. eth1, wlan0, ath0 etc.)

     

    iwpriv [interface] hostapd 1 (used to set card mode to hostapd e.g. for void11)

     

     

    When the monitor mode patch is installed as per the Wireless Build HOWTO the following commands may be used to set the card into monitor mode.

     

     

    iwpriv [interface] monitor [A] [B]


    [A]

    0 = disable monitor mode


    1 = enable monitor mode with Prism2 header
     

    2 = enable monitor mode with no Prism2


    [B]

    Channel to monitor (1-14)

     

    iwlist Commands:

     

    Note: replace [interface] with your interface name as required (e.g. eth1, wlan0, ath0 etc.)

     

    iwlist is used to display some large chunk of information from a wireless network interface that is not displayed by iwconfig.

     

     

    iwlist [interface] scan (Give the list of Access Points and Ad-Hoc cells in range (ESSID, Quality, Frequency, Mode etc.) Note: In tests only worked with Atheros cards).

     

     

    iwlist [interface] channel (Give the list of available frequencies in the device and the number of channels).

     

     

    iwlist [interface] rate (List the bit-rates supported by the device).

     

     

    iwlist [interface] key (List the encryption key sizes supported and display all the encryption keys available in the device).

     

     

    iwlist [interface] power (List the various Power Management attributes and modes of the device).

     

     

    iwlist [interface] txpower (List the various Transmit Power available on the device).

     

     

    iwlist [interface] retry (List the transmit retry limits and retry lifetime on the device).

     

     

    iwlist [interface] ap (Give the list of Access Points in range, and optionally the quality of link to them.  Deprecated in favour of scan)

     

     

    iwlist [interface] peers (Give the list of Peers associated/registered with this card).

     

     

    iwlist [interface] event (List the wireless events supported by this card).

     

     

    Madwifi-ng Commands:

     

    MADWiFi supports virtual access points (VAPS), which means you can create more than one wireless device per wireless card (the host wireless card = wifi0). 

     

    By default, a sta mode VAP is created by, which is MadWifi talk for a 'managed mode wireless interface'.

     

     

    Note: replace athx with your interface name as required (e.g. ath0, ath1)

     

     

    wlanconfig athx destroy (Destroy VAP, athx)

     

     

    wlanconfig athx create wlandev wifi0 wlanmode sta (Create a managed mode VAP, athx)

     

     

    wlanconfig athx create wlandev wifi0 wlanmode ap (Create an Access Point VAP, athx)

     

     

    wlanconfig athx create wlandev wifi0 wlanmode adhoc (Create an Ad-Hoc VAP, athx)

     

     

    wlanconfig athx create wlandev wifi0 wlanmode monitor (Create a Monitor mode VAP, athx)

     

     

    Changing modes:

     

    ifconfig athx down (Take the VAP down)

     


    wlanconfig athx destroy
    (Destroy the VAP, athx)

     


    wlanconfig athx create wlandev wifi0 wlanmode [sta|adhoc|ap|monitor]
    (Create a new sta, adhoc, ap or monitor VAP)

     

     

    Scan for Access Points (requires both steps):

     

    modprobe wlan_scan_sta (To insert the scanning module)

     

     

    wlanconfig athx list scan (To list the APs)

     

     

    For more detailed information, see Madwifi Docs

  • linux下Vi编辑器命令大全

    2007-08-07 18:04:57

    文本编辑器是所有计算机系统中最常用的一种工具。UNIX下的编辑器有ex,sed和vi等,其中,使用最为广泛的是vi,而vi命令繁多,论坛里好像这方面的总结不多,以下稍做总结,以资共享!渴望更正和补充!

    进入vi的命令
    vi filename :打开或新建文件,并将光标置于第一行首
    vi +n filename :打开文件,并将光标置于第n行首
    vi + filename :打开文件,并将光标置于最后一行首
    vi +/pattern filename:打开文件,并将光标置于第一个与pattern匹配的串处
    vi -r filename :在上次正用vi编辑时发生系统崩溃,恢复filename
    vi filename....filename :打开多个文件,依次进行编辑

    移动光标类命令
    h :光标左移一个字符
    l :光标右移一个字符
    space:光标右移一个字符
    Backspace:光标左移一个字符
    k或Ctrl+p:光标上移一行
    j或Ctrl+n :光标下移一行
    Enter :光标下移一行
    w或W :光标右移一个字至字首
    b或B :光标左移一个字至字首
    e或E :光标右移一个字至字尾
    ) :光标移至句尾
    ( :光标移至句首
    }:光标移至段落开头
    {:光标移至段落结尾
    nG:光标移至第n行首
    n+:光标下移n行
    n-:光标上移n行
    n$:光标移至第n行尾
    H :光标移至屏幕顶行
    M :光标移至屏幕中间行
    L :光标移至屏幕最后行
    0:(注意是数字零)光标移至当前行首
    $:光标移至当前行尾

    屏幕翻滚类命令
    Ctrl+u:向文件首翻半屏
    Ctrl+d:向文件尾翻半屏
    Ctrl+f:向文件尾翻一屏
    Ctrl+b;向文件首翻一屏
    nz:将第n行滚至屏幕顶部,不指定n时将当前行滚至屏幕顶部。

    插入文本类命令
    i :在光标前
    I :在当前行首
    a:光标后
    A:在当前行尾
    o:在当前行之下新开一行
    O:在当前行之上新开一行
    r:替换当前字符
    R:替换当前字符及其后的字符,直至按ESC键
    s:从当前光标位置处开始,以输入的文本替代指定数目的字符
    S:删除指定数目的行,并以所输入文本代替之
    ncw或nCW:修改指定数目的字
    nCC:修改指定数目的行

    删除命令
    ndw或ndW:删除光标处开始及其后的n-1个字
    do:删至行首
    d$:删至行尾
    ndd:删除当前行及其后n-1行
    x或X:删除一个字符,x删除光标后的,而X删除光标前的
    Ctrl+u:删除输入方式下所输入的文本

    搜索及替换命令
    /pattern:从光标开始处向文件尾搜索pattern
    ?pattern:从光标开始处向文件首搜索pattern
    n:在同一方向重复上一次搜索命令
    N:在反方向上重复上一次搜索命令
    :s/p1/p2/g:将当前行中所有p1均用p2替代
    :n1,n2s/p1/p2/g:将第n1至n2行中所有p1均用p2替代
    :g/p1/s//p2/g:将文件中所有p1均用p2替换

    选项设置
    all:列出所有选项设置情况
    term:设置终端类型
    ignorance:在搜索中忽略大小写
    list:显示制表位(Ctrl+I)和行尾标志($)
    number:显示行号
    report:显示由面向行的命令修改过的数目
    terse:显示简短的警告信息
    warn:在转到别的文件时若没保存当前文件则显示NO write信息
    nomagic:允许在搜索模式中,使用前面不带“\”的特殊字符
    nowrapscan:禁止vi在搜索到达文件两端时,又从另一端开始
    mesg:允许vi显示其他用户用write写到自己终端上的信息

    最后行方式命令
    :n1,n2 co n3:将n1行到n2行之间的内容拷贝到第n3行下
    :n1,n2 m n3:将n1行到n2行之间的内容移至到第n3行下
    :n1,n2 d :将n1行到n2行之间的内容删除
    :w :保存当前文件
    :e filename:打开文件filename进行编辑
    :x:保存当前文件并退出
    :q:退出vi
    :q!:不保存文件并退出vi
    :!command:执行shell命令command
    :n1,n2 w!command:将文件中n1行至n2行的内容作为command的输入并执行之,若不指定n1,n2,则表示将整个文件内容作为command的输入
    :r!command:将命令command的输出结果放到当前行

    寄存器操作
    "?nyy:将当前行及其下n行的内容保存到寄存器?中,其中?为一个字母,n为一个数字
    "?nyw:将当前行及其下n个字保存到寄存器?中,其中?为一个字母,n为一个数字
    "?nyl:将当前行及其下n个字符保存到寄存器?中,其中?为一个字母,n为一个数字
    "?p:取出寄存器?中的内容并将其放到光标位置处。这里?可以是一个字母,也可以是一个数字
    ndd:将当前行及其下共n行文本删除,并将所删内容放到1号删除寄存器中。

    VI的使用
    --------------------------------------------------------------------------------


    一、插入文本
    ┌──┬────────────┐
    │命令│描述          
    ├──┼────────────┤
    │i  │在当前字符前插入文本  
    ├──┼────────────┤
    │I  │在行首插入文本      
    ├──┼────────────┤
    │a  │在当前字符后添加文本  
    ├──┼────────────┤
    │A  │在行末添加文本     
    ├──┼────────────┤
    │o  │在当前行后面插入一空行 
    ├──┼────────────┤
    │O  │在当前行前面插入一空行 
    ├──┼────────────┤
    │R  │以改写方式输入文本   
    └──┴────────────┘
    二、移动光标
    ┌─────┬───────────┐
    │命令   │描述        
    ├─────┼───────────┤
    │j或下箭头    │向下移动一行    
    ├─────┼───────────┤
    │k或上箭头    │向上移动一行     
    ├─────┼───────────┤
    │h或左箭头    │左移一个字符    
    ├─────┼───────────┤
    │l或右箭头    │右移一个字符    
    ├─────┼───────────┤
    │w     │右移一个词      
    ├─────┼───────────┤
    │W     │右移一个以空格分隔的词
    ├─────┼───────────┤
    │b     │左移一个词      
    ├─────┼───────────┤
    │B     │左移一个以空格分隔的词
    ├─────┼───────────┤
    │0     │移到行首 
          
    │Ctrl-F   │向前翻页       
    ├─────┼───────────┤
    │Ctrl-B   │向后翻页       
    ├─────┼───────────┤
    │nG    │到第n行        
    ├─────┼───────────┤
    │G     │到最后一行      
    └─────┴───────────┘

    三、替换文本
    ┌─────┬──────┐
    │命令   │描述    
    ├─────┼──────┤
    │$     │到行尾   
    ├─────┼──────┤
    │(     │到句子的开头
    ├─────┼──────┤
    │)     │到句子的末尾
    ├─────┼──────┤
    │{     │到段落的开头
    ├─────┼──────┤
    │}     │到段落的末尾
    └─────┴──────┘

    四、删除文本
    ┌───┬───────────┐
    │命令 │描述          
    ├───┼───────────┤
    │r   │替换一个字符     
    ├───┼───────────┤
    │c   │修改文本直到按下Esc健
    ├───┼───────────┤
    │cw  │修改下一个词      
    ├───┼───────────┤
    │cnw  │修改接下来的n个词  


    五、文本编辑
    ┌──┬──────────────────────┐
    │命寺│描述                    
    ├──┼──────────────────────┤
    │yy  │将一行文本移到缺省缓冲区中          
    ├──┼──────────────────────┤
    │yn  │将下一个词移到缺省缓冲区中          
    ├──┼──────────────────────┤
    │ynw   │将后面的n个词移到缺省缓冲区中        
    ├──┼──────────────────────┤
    │p   │如果缺省缓冲区中包含一行文本,则在当前   
    │  │行后面插入一个空行井将缺省缓冲区中的声  
    │  │容粘贴到这一行中;如果缺省缓冲区中包含   
    │  │多个词,把这些词粘贴到光标的右边.     
    ├──┼──────────────────────┤
    │P   │如果缺省缓冲区中包含一行文本,则正当前    
    │    │行前面插入一个空行井将缺省缓冲区中的内    
    │   │容粘贴到这一行中;如果缺省缓冲区中包含   
    │    │多个词,把这些词粘贴到光标的左边       
    └──┴──────────────────────┘


    六、保存退出
    ┌───────────┬───────────────┐
    │命令         │描述            
    ├───────────┼───────────────┤
    │zz      │保存并退出          
    ├───────────┼───────────────┤
    │:w filename   │写入文件           
    ├───────────┼───────────────┤
    │:W      │写入文件          
    ├───────────┼───────────────┤
    │:x      │保存(如果当前文件修改过)并退出
    ├───────────┼───────────────┤
    │:q!       │不保存文件,直接退出     
    ├───────────┼───────────────┤
    │:q       │退出vi            
    └───────────┴───────────────┘
     




    VI常用技巧

    VI命令可以说是Unix/Linux世界里最常用的编辑文件的命令了,但是因为它的命令集众多,很多人都不习惯使用它,其实您只需要掌握基本命令,然后加以灵活运用,就会发现它的优势,并会逐渐喜欢使用这种方法。本文旨在介绍VI的一些最常用命令和高级应用技巧。

    一、基本命令介绍

    ---- 1.光标命令
    k、j、h、l——上、下、左、右光标移动命令。虽然您可以在Linux中使用键盘右边的4个光标键,但是记住这4个命令还是非常有用的。这4个键正是右手在键盘上放置的基本位置。
    nG——跳转命令。n为行数,该命令立即使光标跳到指定行。
    Ctrl+G——光标所在位置的行数和列数报告。
    w、b——使光标向前或向后跳过一个单词。

    ---- 2.编辑命令
    i、a、r——在光标的前、后以及所在处插入字符命令(i=insert、a=append、r=replace)。
    cw、dw——改变(置换)/删除光标所在处的单词的命令 (c=change、d=delete)。
    x、d$、dd——删除一个字符、删除光标所在处到行尾的所有字符以及删除整行的命令。

    ---- 3.查找命令
    ---- /string、?string——从光标所在处向后或向前查找相应的字符串的命令。

    ---- 4.拷贝复制命令
    ---- yy、p——拷贝一行到剪贴板或取出剪贴板中内容的命令。

    二、常见问题及应用技巧

    ---- 1.在一个新文件中读/etc/passwd中的内容,取出用户名部分。
    ---- vi file
    ---- :r /etc/passwd 在打开的文件file中光标所在处读入/etc/passwd
    ---- :%s/:.*//g 删除/etc/passwd中用户名后面的从冒号开始直到行尾的所有部分。
    ---- 您也可以在指定的行号后读入文件内容,例如使用命令“:3r /etc/passwd”从新文件的第3行开始读入 /etc/passwd的所有内容。
    ---- 我们还可以使用以下方法删掉文件中所有的空行及以#开始的注释行。
    ---- #cat squid.conf.default | grep -v ^$ | grep -v ^#

    ---- 2.在打开一个文件编辑后才知道登录的用户对该文件没有写的权限,不能存盘,需要将所做修改存入临时文件。
    ---- vi file
    ---- :w /tmp/1 保存所做的所有修改,也可以将其中的某一部分修改保存到临时文件,例如仅仅把第20~59行之间的内容存盘成文件/tmp/1,我们可以键入如下命令。
    ---- vi file
    ---- :20,59w /tmp/1

    ---- 3.用VI编辑一个文件,但需要删除大段的内容。
    ---- 首先利用编辑命令“vi file”打开文件,然后将光标移到需要删除的行处按Ctrl+G显示行号,再到结尾处再按Ctrl+G,显示文件结尾的行号。
    ---- :23,1045d 假定2次得到的行号为23和1045,则把这期间的内容全删除,也可以在要删除的开始行和结束行中用ma、mb命令标记,然后利用“:a,bd”命令删除。

    ---- 4.在整个文件的各行或某几行的行首或行尾加一些字符串。
    ---- vi file
    ---- :3,$s/^/some string / 在文件的第一行至最后一行的行首插入“some string”。
    ---- :%s/$/some string/g 在整个文件每一行的行尾添加“some string”。
    ---- :%s/string1/string2/g 在整个文件中替换“string1”成“string2”。
    ---- :3,7s/string1/string2/ 仅替换文件中的第3行到第7行中的“string1”成“string2”。
    ---- 注意: 其中s为substitute,%表示所有行,g表示global。

    ---- 5.同时编辑2个文件,拷贝一个文件中的文本并粘贴到另一个文件中。
    ---- vi file1 file2
    ---- yy 在文件1的光标处拷贝所在行
    ---- :n 切换到文件2 (n=next)
    ---- p 在文件2的光标所在处粘贴所拷贝的行
    ---- :n 切换回文件1

    ---- 6.替换文件中的路径。
    ---- 使用命令“:%s#/usr/bin#/bin#g”可以把文件中所有路径/usr/bin换成/bin。也可以使用命令“:%s//usr/bin//bin/g”实现,其中“”是转义字符,表明其后的“/”字符是具有实际意义的字符,不是分隔符。
  • Linux 常用命令-系统管理

    2007-08-07 18:03:39

    free命令

    free命令的功能是查看当前系统内存的使用情况,它显示系统中剩余及已用的物理内存和交换内存,以及共享内存和被核心使用的缓冲区。

    该命令的一般格式为: free [-b | -k | -m] 命令中各选项的含义如下:

    -b 以字节为单位显示。
    -k
    K字节为单位显示。
    -m
    以兆字节为单位显示。


    shutdown命令

    shutdown 命令可以安全地关闭或重启Linux系统,它在系统关闭之前给系统上的所有登录用户提示一条警告信息。该命令还允许用户指定一个时间参数,可以是一个精确的时间,也可以是从现在开始的一个时间段。精确时间的格式是hh:mm,表示小时和分钟;时间段由“+”和分钟数表示。系统执行该命令后,会自动进行数据同步的工作。

    该命令的一般格式为: shutdown [选项] [时间] [警告信息] 命令中各选项的含义为:

    - k 并不真正关机,而只是发出警告信息给所有用户。
    - r 关机后立即重新启动。
    - h
    关机后不重新启动。
    - f 快速关机,重启动时跳过fsck
    - n 快速关机,不经过init程序。
    - c
    取消一个已经运行的shutdown

    需要特别说明的是,该命令只能由超级用户使用。


    sync命令

    sync命令是在关闭Linux系统时使用的。 用户需要注意的是,不能用简单的关闭电源的方法关闭系统,因为Linux象其他Unix系统一样,在内存中缓存了许多数据,在关闭系统时需要进行内存数据与硬盘数据的同步校验,保证硬盘数据在关闭系统时是最新的,只有这样才能确保数据不会丢失。一般正常的关闭系统的过程是自动进行这些工作的,在系统运行过程中也会定时做这些工作,不需要用户干预。 sync命令是强制把内存中的数据写回硬盘,以免数据的丢失。用户可以在需要的时候使用此命令。该命令的一般格式为: sync

  • nagios监控系统搭建

    2007-05-31 17:31:20

    近段时间一直在研究nagios监控系统,借鉴了不少高手的文章,费了不少功夫总算把它搞定了,以下就是具体的安装过程,更深层的监控项目还有 待进一步研究,希望大家给点建设!!
    #===========================安装 Nagios    ========================================
    cd /opt
    groupadd nagios
    groupadd nagcmd
    useradd nagios -g nagcmd -d /usr/local/nagios
    chown nagios.nagios /usr/local/nagios/
    chmod 755 /usr/local/nagios
    wget http://osdn.dl.sourceforge.net/sourceforge/nagios/nagios-3.0.2.tar.gz
    tar -zxvf nagios-3.0.2.tar.gz
    cd nagios-3.0.2
    ./configure --with-command-group=nagcmd
    make all
    make install
    make install-init
    make install-config
    make install-commandmode
    /usr/local/apache2/bin/htpasswd -c /usr/local/nagios/etc/htpasswd.users nagiosadmin
    #编辑httpd.conf配置文件
    ScriptAlias /nagios/cgi-bin "/usr/local/nagios/sbin"
    <Directory "/usr/local/nagios/sbin">
    # SSLRequireSSL
       Options ExecCGI
       AllowOverride None
       Order allow,deny
       Allow from all
    # Order deny,allow
    # Deny from all
    # Allow from 127.0.0.1
       AuthName "Nagios Access"
       AuthType Basic
       AuthUserFile /usr/local/nagios/etc/htpasswd.users
       Require valid-user
    </Directory>
    Alias /nagios "/usr/local/nagios/share"
    <Directory "/usr/local/nagios/share">
    # SSLRequireSSL
       Options None
       AllowOverride None
       Order allow,deny
       Allow from all
    # Order deny,allow
    # Deny from all
    # Allow from 127.0.0.1
       AuthName "Nagios Access"
       AuthType Basic
       AuthUserFile /usr/local/nagios/etc/htpasswd.users
       Require valid-user
    </Directory>
    #重启apache
    killall httpd
    #============================安装nagios plugins=========================
    cd /opt
    wget http://osdn.dl.sourceforge.net/sourceforge/nagiosplug/nagios-plugins-1.4.11.tar.gz
    tar -zxvf nagios-plugins-1.4.11.tar.gz
    cd nagios-plugins-1.4.11
    ./configure --prefix=/usr/local/nagios --with-nagios-user=nagios --with-nagios-gourp=nagios --with-mysql=/usr/local/mysql5/ --enable-perl-modules
    make
    make install
    #配置和启动nagios
    /usr/local/nagios/bin/nagios -v /usr/local/nagios/etc/nagios.cfg
    #如果提示“Whoops!   Error: Could not read object configuration data! ”,这是因为没有启动nagios后台进程,执行以下命令
    /usr/local/nagios/bin/nagios -d /usr/local/nagios/etc/nagios.cfg
    #=============================安装nrpe 插件==============================
    #对远程一台linux主机进行监控
    #监控服务器上安装设置
    cd /opt
    wget http://www.mirrors.wiretapped.net/security/network-monitoring/nagios/nrpe-2.8.1.tar.gz
    #监控主机上安装方法
    tar -zxvf nrpe-2.8.1.tar.gz
    cd nrpe-2.8.1
    ./configure
    make all
    make install
    make install-plugin    ---监控机需要安装check_nrpe这个插件,被监控机并不需要
    ##########################################################################
    #被监控主机上安装方法
    cd /opt
    useradd nagios -d /usr/local/nagios
    chown nagios.nagios /usr/local/nagios/
    tar -zxvf nagios-plugins-1.4.11.tar.gz
    cd nagios-plugins-1.4.11
    ./configure --enable-perl-modules --with-ping-command=ping
    make
    make install
    cd /opt
    tar -zxvf nrpe-2.8.1.tar.gz
    cd nrpe-2.8.1
    ./configure
    make all
    make install
    make install-plugin
    make install-daemon
    make install-daemon-config

    vi /usr/local/nagios/etc/nrpe.conf
    allowed_hosts=192.168.8.150
    #为了监控swap在nrpe.cfg中添加
    command[check_swap]=/usr/local/nagios/libexec/check_swap -w 20% -c 10%
    command[check_sda1]=/usr/local/nagios/libexec/check_disk -w 20 -c 10 -p /dev/sda1
    #sdb1可根椐实际情况更改,我这里的硬盘类型是scsi
    vi /etc/services
    #增加nrpe
    nrpe            5666/tcp                        # nrpe
    #启动nrpe
    /usr/local/nagios/bin/nrpe -c /usr/local/nagios/etc/nrpe.cfg -d
    netstat -at|grep nrpe
    netstat -an|grep 5666
    #############################################################################
    #再对监控服务器进行设置
    #添加nrpe的定义
    vi /usr/local/nagios/etc/objects/commands.cfg
    # 'check_nrpe ' command definition
    define command{
            command_name check_nrpe
            command_line $USER1$/check_nrpe -H $HOSTADDRESS$ -c $ARG1$
            }
    vi /usr/local/nagios/etc/cgi.cfg
    把use_authentication=1修改为use_authentication=0如果出现页面无法显示之类的
    authorized_for_system_commands=nagiosadmin,kerry    --kerry为http访问授权用户
    authorized_for_all_services=nagiosadmin,kerry
    authorized_for_all_hosts=nagiosadmin,kerry
    authorized_for_all_service_commands=nagiosadmin,kerry
    authorized_for_all_host_commands=nagiosadmin,kerry
    #测试NRPE是否则正常工作
    /usr/local/nagios/libexec/check_nrpe -H localhost
    killall nagios
    /usr/local/nagios/bin/nagios -d /usr/local/nagios/etc/nagios.cfg
    echo "/usr/local/nagios/bin/nrpe -c /usr/local/nagios/etc/nrpe.cfg –d" >> /etc/rc.local
    ehco "/usr/local/nagios/bin/nagios -d /usr/local/nagios/etc/nagios.cfg" >>/etc/rc.local
    #问题:notifications for this service have been disabled
    #解决办法:enable notifications for this service
    #问题:Status Map页面无法显示
    #解决方法:ln -s /usr/local/lib/libgd.so.2 /usr/lib/libgd.so.2
    vi /usr/local/nagios/etc/nagios.cfg
    #添加
    cfg_file=/usr/local/nagios/etc/objects/emos-mailserver.cfg
    #emos-mailserver.cfg这个文件名可以自定义
    #对刚定义的emos-mailserver.cfg文件进行配置
    vi /usr/local/nagios/etc/objects/emos-mailserver.cfg
    define host{
               use          linux-server
              host_name     emos-mailserver
              alias         emos-mailserver
              address       192.168.8.151
            }
    define service{
            use             generic-service
            host_name       emos-mailserver
            service_description     HTTP
            check_command     check_http
           }
    define service{
            use             generic-service
            host_name       emos-mailserver
            service_description     SSH
            check_command   check_ssh
           }
    define service{
            use             generic-service
            host_name       emos-mailserver
            service_description     SMTP
            check_command   check_smtp
           }
    define service{
            use             generic-service
            host_name       emos-mailserver
            service_description     POP3
            check_command   check_pop
           }
    #define service{
    #        use             generic-service
    #        host_name       emos-mailserver
    #        service_description     mysql
    #        check_command   check_mysql
    #       }
    define service{
            use             generic-service
            host_name       emos-mailserver
            service_description     check-swap
            check_command           check_nrpe!check_swap
            }
    define service{
            use             generic-service
            host_name       emos-mailserver
            service_description     check-load
            check_command           check_nrpe!check_load
            }
    define service{
            use             generic-service
            host_name       emos-mailserver
            service_description     check-disk
            check_command           check_nrpe!check_sda1
    }
    define service{
            use             generic-service
            host_name       emos-mailserver
            service_description     zombie_procs
            check_command           check_nrpe!check_zombie_procs
            }
    define service{
            use             generic-service
            host_name       emos-mailserver
            service_description     check-users
            check_command           check_nrpe!check_users
            }
    define service{
            use             generic-service
            host_name       emos-mailserver
            service_description     total_procs
            check_command           check_nrpe!check_total_procs
    #配置完后,重启nagios
    killall nagios
    service nagios start
    pstree |grep nagios

    #=============================安装 pnp===============================================
    cd /opt
    tar zxvf pnp-0.4.12.tar.gz
    cd pnp-0.4.12
    ./configure --with-nagios-user=nagios \
    --with-nagios-group-nagios \
    --with-rrdtool=/usr/local/rrdtool/bin/rrdtool \
    --with-perfdata-dir=/usr/local/nagios/share/perfdata
    make
    make all
    make install
    make install-config
    make install-init
    #错误提示:RRDs Perl Modules:                *** NOT FOUND ***
    #解决方法:ln -sv /usr/local/rrdtool/lib/perl/5.8.8/i386-linux-thread-multi/auto/RRDs/RRDs.so /usr/lib/perl5/5.8.8/i386-linux-thread-multi/
    #要产生图形数据还需在commands.cfg中重定义命令:
    define command{
    command_name process-service-perfdata
    command_line /usr/local/nagios/libexec/process_perfdata.pl
    }
    #产生图形数据还要设定nagios.cfg文件:
    process_performance_data=1
    service_perfdata_command=process-service-perfdata
    #==========================监控远程windows主 机==================================
    #监控windows服务器
    wget http://nchc.dl.sourceforge.net/sourceforge/nscplus/NSClient++-Win32-0.3.5.zip
    解压nsclient++0.3.3.zip到c盘根目录
    解压为C:\NSClient++
    #在nagios监控服务器上
    vi /usr/local/nagios/etc/nagios.cfg
    cfg_file=/usr/local/nagios/etc/objects/windows.cfg 去掉这句话的注释
    #到win服务器上,打开命令窗口,cd到刚才解压的目录
    #在命令行界面执行 nsclient++ /install
    #然后 nsclient++ SysTray 如果出错不用管!
    #此时在“服务”里面已经有了nsclient的服务
    双击打开,点"登录"标签,在"允许服务与桌面交互"前打勾
    #编辑NES.ini,在 [modules] 选项里,去掉所有的注释符号; 除了
    CheckWMI.dll和RemoteConfiguration.dll
    #the [Settings] 选项里
    修改allowd_host=192.168.8.150(nagios服务器的ip)
    #[NSClient] 里面,去掉port=12489的注释!他靠端口12489侦听,所以防火墙要打开这个端口!
    在[Settings]部分设置'password'选项 来设置密码,作用是在nagios连接过来时要求提供密码.这一步是可选的,我这里方便起见跳过它,不要密码.
    #然后启动nsclient
    nsclient++ /start
    #接下来我们开始配置nagios服务器里面的内容
    vi /usr/local/nagios/etc/objects/windows.cfg
    define host{
        use windows-server
        host_name winserver alias
        My Windows Server
        address 192.168.8.151   --windows服务器的IP地址
        }
    #修改hostname和address,很重要!!
    #重新启动监控服务器上的nagios
    killall nagios
    service nagios start
    #==============================安装 sengEmail ==================================
    #使用sendEmail发送报警邮件
    cd /opt
    wget http://caspian.dotconf.net/menu/Software/SendEmail/sendEmail-v1.55.tar.gz
    tar -zxvf sendEmail-v1.55.tar.gz
    cd sendEmail-v1.55
    cp sendEmail /usr/local/bin
    chmod +x /usr/local/bin/sendEmail
    #sendEmail使用方法
    /usr/local/bin/sendEmail –f kerry.hu@3aaa.com –t kerry.hu@3aaa.com –s mail.3aaa.com –u “from nagios” –xu kerry.hu@3aaa.com –xp 11111 –m happy
    #解释:-f 表示发送者的邮箱
         -t 表示接收者的邮箱
         -s 表示SMTP服务器的域名或者ip
         -u 表示邮件的主题
         -xu 表示SMTP验证的用户名
         -xp 表示SMTP验证的密码(注意,这个密码貌似有限制,例如我用d!5neyland就不能被正确识别)
         -m 表示邮件的内容如果你不带-m参数的话,就会提示你自行输入
    #编辑配置文件,nagios使用sendEmail来发警告邮件
    vi /usr/local/nagios/etc/objects/commands.cfg
    # 'notify-host-by-email' command definition
    define command{
    command_name notify-host-by-email
    command_line /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /usr/local/bin/sendEmail –f kerry.hu@3aaa.com –t $CONTACTEMAIL$ –s mail.3aaa.com –u "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" –xu kerry.hu@3aaa.com –xp 11111
    }
    # 'notify-service-by-email' command definition
    define command{
    command_name notify-service-by-email
    command_line /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\n\nService: $SERVICEDESC$\nHost: $HOSTALIAS$\nAddress: $HOSTADDRESS$\nState: $SERVICESTATE$\n\nDate/Time: $LONGDATETIME$\n\nAdditional Info:\n\n$SERVICEOUTPUT$" | /usr/local/bin/sendEmail –f kerry.hu@3aaa.com –t $CONTACTEMAIL$ –s mail.3aaa.com –u "** $NOTIFICATIONTYPE$ Service Alert: $HOSTALIAS$/$SERVICEDESC$ is $SERVICESTATE$ **" –xu kerry.hu@3aaa.com –xp 111111
    }
    #注:在使用sendEmail的过程中无法发送报警邮件,不知是什么原因,只好改用系统自带的sendmail发邮 件!!

    http://workaround.org/squid/nagios-plugin/check_squid的脚本用来检测squid很好用,但有时 会报错:

    Parsing of undecoded UTF-8 will give garbage when decoding entities at /usr/lib/perl5/vendor_perl/5.8.5/LWP/Protocol.pm line 114.

    原因是:
    HTML::HeadParser模块在使用parse()方法时,对没有编码的UTF-8会弄混,要保证在传值之前进行适当的编码。
    参考:http://www.xinjiezuo.com/blog/?p=43

    解决方式是加入一行:

    $ua->parse_head(0);

    跳过去就好了。

    下面是改过后的check_squid.


    #!/usr/bin/perl -w
    #
    # check_squid - Nagios check plugin for testing a Squid proxy
    #
    # Christoph Haas (email@christoph-haas.de)
    # License: GPL 2
    #
    # V0.2
    #

    use LWP::UserAgent;
    use HTTP::Request::Common qw(POST GET);
    use HTTP::Headers;
    use strict;

    my ($url, $urluser, $urlpass, $proxy, $proxyport,
            $proxyuser, $proxypass, $expectstatus) = @ARGV;

    unless ($url && $proxy && $expectstatus)
    {
            print "Usage: url urluser urlpass proxy proxyport proxyuser proxypass expectstatus\n";
                                    print " url       -> The URL to check on the internet (http://www.google.com)\n";
                                    print " urluser   -> Username if the web site required authentication (- = none)\n";
                                    print " urlpass   -> Password if the web site required authentication (- = none)\n";
                                    print " proxy     -> Server that squid runs on (proxy.mydomain)\n";
                                    print " proxyport -> TCP port that Squid listens on (3128)\n";
                                    print " proxyuser -> Username if the web site required authentication (- = none)\n";
                                    print " proxypass -> Password if the web site required authentication (- = none)\n";
                                    print " expectstatus -> HTTP code that should be returned\n";
                                    print "                  (2 = anything that begins with 2)\n";
            exit -1;
    }

    $urluser='' if $urluser eq '-';
    $urlpass='' if $urlpass eq '-';
    $proxyuser='' if $proxyuser eq '-';
    $proxypass='' if $proxypass eq '-';

    my $ua = new LWP::UserAgent;
    $ua->parse_head(0); //加入这行就好了

    my $h = HTTP::Headers->new();

    if ($proxy)
    {
            $ua->proxy(['http', 'ftp'], "http://$proxy:$proxyport");

            if ($proxyuser)
            {
                    $h->proxy_authorization_basic($proxyuser,$proxypass);
            }
    }

    if ($urluser)
    {
            $h->authorization_basic($urluser, $urlpass);
    }

    my $req = HTTP::Request->new('GET', $url, $h);

    my $res = $ua->request($req);

    if ($res->status_line =~ /^$expectstatus/)
    {
            print "OK - Status: ".$res->status_line."\n";
            exit 0;
    }
    else
    {
            print "WARNING - Status: ".$res->status_line." (but expected $expectstatus...)\n";
            exit 1;
    }
    http://www.nagios.org/download 查考
  • 商业门户站点的十二个关键特性

    2007-02-07 14:54:57

    商业门户站点的十二个关键特性

    商业 Web 站点的特性能够决定项目的成功与否。我们将在这里点出一些关键特性,这些特性将对你的 intranet 或 extranet 的部署有所帮助。

    所谓门户,简单的说,就是一个专门用来收集和组织相关信息及运营数据的 Web 站点。为公司新 Web 站点制定一个策略,或者改进原有的公司 Web 站点,比简单地开通一个 Web 页面要复杂一点儿。

    商业门户战略的第一部分应该决定你所面向的用户。如果你面向的用户是你在 extranet 上的客户和合作伙伴,那么你需要考虑在你的站点使用某个特性集。如果你的 intranet 门户的目标用户是自己公司的职员,那么你需要考虑为你的站点使用另外一个特性集。下面我们来分析这两种不同的商业门户庀个策略,或者改进原有的公司 Web 站点,比简单地开通一个 Web 页面要复杂一点儿。

    商业门户战略的第一部分应该决定你所面向的用户。如果你面向的用户是你在 extranet 上的客户和合作伙伴,那么你需要考虑在你的站点使用某个特性集。如果你的 intranet 门户的目标用户是自己公司的职员,那么你需要考虑为你的站点使用另外一个特性集。下面我们来分析这两种不同的商业门户庀个策略,或者改进原有的公司 Web 站点,比简单地开通一个 Web 页面要复杂一点儿。

    商业门户战略的第一部分应该决定你所面向的用户。如果你面向的用户是你在 extranet 上的客户和合作伙伴,那么你需要考虑在你的站点使用某个特性集。如果你的 intranet 门户的目标用户是自己公司的职员,那么你需要考虑为你的站点使用另外一个特性集。下面我们来分析这两种不同的商业门户应用程序,以帮助你制定商业门户战略。

    针对 extranet 的特性

    对于哪些特性适合门户设计没有绝对的答案。尽管大家对这一问题没有定论,但是大家都一致认为在架设 extranet 门户站点时有一些重要的特性需要考虑。这些特性包括:

    •搜索功能:商业门户应该能够根据关键字来查询结构化及非结构化的内容。结构化数据是指数据库和事务性系统,比如 ERP 系统。而非结构奔用程序,以帮助你制定商业门户战略。

    针对 extranet 的特性

    对于哪些特性适合门户设计没有绝对的答案。尽管大家对这一问题没有定论,但是大家都一致认为在架设 extranet 门户站点时有一些重要的特性需要考虑。这些特性包括:

    •搜索功能:商业门户应该能够根据关键字来查询结构化及非结构化的内容。结构化数据是指数据库和事务性系统,比如 ERP 系统。而非结构奔用程序,以帮助你制定商业门户战略。

    针对 extranet 的特性

    对于哪些特性适合门户设计没有绝对的答案。尽管大家对这一问题没有定论,但是大家都一致认为在架设 extranet 门户站点时有一些重要的特性需要考虑。这些特性包括:

    •搜索功能:商业门户应该能够根据关键字来查询结构化及非结构化的内容。结构化数据是指数据库和事务性系统,比如 ERP 系统。而非结构굔用程序,以帮助你制定商业门户战略。

    针对 extranet 的特性

    对于哪些特性适合门户设计没有绝对的答案。尽管大家对这一问题没有定论,但是大家都一致认为在架设 extranet 门户站点时有一些重要的特性需要考虑。这些特性包括:

    •搜索功能:商业门户应该能够根据关键字来查询结构化及非结构化的内容。结构化数据是指数据库和事务性系统,比如 ERP 系统。而非结构化内容包括所有的office文档、提案以及其它一些不能很方便地输入到数据库的信息。

    •一致,易用的界面:门户一般具有一致的界面,通过这个界面可以从主页进入门户的各个部分。这个一致的界面一般是专门为简化门户使用而设计的。其中可能包括链接到门户更高层的breadcrumbing,还可能包括一个可以扩展成一串链接的悬停菜单。

    •最小客户端部署:门户一般不要求甌?内容包括所有的office文档、提案以及其它一些不能很方便地输入到数据库的信息。

    •一致,易用的界面:门户一般具有一致的界面,通过这个界面可以从主页进入门户的各个部分。这个一致的界面一般是专门为简化门户使用而设计的。其中可能包括链接到门户更高层的breadcrumbing,还可能包括一个可以扩展成一串链接的悬停菜单。

    •最小客户端部署:门户一般不要求用户安装新的软件。这通常意味着门户是基于 Web 的应用程序。

    •讨论:一些门户会提供讨论论坛,用户可以在这个论坛里面与其它人或者门户主管方交互。这些论坛是为增强用户和门户主管方的关系而设计的。

    •聚集:将链接和内容抽出来放到一个单独的地方能够帮助用户了解该去哪里寻找信息。聚集允许用户通过一个用户界面与多个系统交互。

    •告警:用户安装新的软件。这通常意味着门户是基于 Web 的应用程序。

    •讨论:一些门户会提供讨论论坛,用户可以在这个论坛里面与其它人或者门户主管方交互。这些论坛是为增强用户和门户主管方的关系而设计的。

    •聚集:将链接和内容抽出来放到一个单独的地方能够帮助用户了解该去哪里寻找信息。聚集允许用户通过一个用户界面与多个系统交互。

    •告警:用户能够注册一个电子邮件,在他们所感兴趣的信息发生变化时,他们会收到电子邮件通知。这可以即包括关键性能指示器的变化,也包括文档内容的变化。告警改变了用户通过 pull 模式与 Web 站点进行交互的模式,在 pull 模式下,用户必须去查看门户,而在这种新型的 push 模式下,当门户中用户所感兴趣的部分发生变化时他们会收到通知。

    •自服务:门户可以是很多自服务庸户能够注册一个电子邮件,在他们所感兴趣的信息发生变化时,他们会收到电子邮件通知。这可以即包括关键性能指示器的变化,也包括文档内容的变化。告警改变了用户通过 5??内容包括所有的office文档、提案以及其它一些不能很方便地输入到数据库的信息。

    •一致,易用的界面:门户一般具有一致的界面,通过这个界面可以从主页进入门户的各个部分。这个一致的界面一般是专门为简化门户使用而设计的。其中可能包括链接到门户更高层的breadcrumbing,还可能包括一个可以扩展成一串链接的悬停菜单。

    •最小客户端部署:门户一般不要求甌?内容包括所有的office文档、提案以及其它一些不能很方便地输入到数据库的信息。

    •一致,易用的界面:门户一般具有一致的界面,通过这个界面可以从主页进入门户的各个部分。这个一致的界面一般是专门为简化门户使用而设计的。其中可能包括链接到门户更高层的breadcrumbing,还可能包括一个可以扩展成一串链接的悬停菜单。

    •最小客户端部署:门户一般不要求用户安装新的软件。这通常意味着门户是基于 Web 的应用程序。

    •讨论:一些门户会提供讨论论坛,用户可以在这个论坛里面与其它人或者门户主管方交互。这些论坛是为增强用户和门户主管方的关系而设计的。

    •聚集:将链接和内容抽出来放到一个单独的地方能够帮助用户了解该去哪里寻找信息。聚集允许用户通过一个用户界面与多个系统交互。

    •告警:用户安装新的软件。这通常意味着门户是基于 Web 的应用程序。

    •讨论:一些门户会提供讨论论坛,用户可以在这个论坛里面与其它人或者门户主管方交互。这些论坛是为增强用户和门户主管方的关系而设计的。

    •聚集:将链接和内容抽出来放到一个单独的地方能够帮助用户了解该去哪里寻找信息。聚集允许用户通过一个用户界面与多个系统交互。

    •告警:用户能够注册一个电子邮件,在他们所感兴趣的信息发生变化时,他们会收到电子邮件通知。这可以即包括关键性能指示器的变化,也包括文档内容的变化。告警改变了用户通过 pull 模式与 Web 站点进行交互的模式,在 pull 模式下,用户必须去查看门户,而在这种新型的 push 模式下,当门户中用户所感兴趣的部分发生变化时他们会收到通知。

    •自服务:门户可以是很多自服务庸户能够注册一个电子邮件,在他们所感兴趣的信息发生变化时,他们会收到电子邮件通知。这可以即包括关键性能指示器的变化,也包括文档内容的变化。告警改变了用户通过 pull 模式与 Web 站点进行交互的模式,在 pull 模式下,用户必须去查看门户,而在这种新型的 push 模式下,当门户中用户所感兴趣的部分发生变化时他们会收到通知。

    •自服务:门户可以是很多自服务应用程序的一个家园,这里允许客户、职员和其他人做他们自己的事情。

    使用上面列出的这些特性,你就可以创建出一个交互式环境。在你创建的这个交互式环境中,你的客户或其他合作伙伴能够很容易地找到他们需要的东西,并且可以很容易地与你的门户交互,从而增加你的 extranet 的用户量。

    针对 intranet 的特性

    除了上面所提及的针对 extranet 的特性之外,在部署 intranet 时!?用程序的一个家园,这里允许客户、职员和其他人做他们自己的事情。

    使用上面列出的这些特性,你就可以创建出一个交互式环境。在你创建的这个交互式环境中,你的客户或其他合作伙伴能够很容易地找到他们需要的东西,并且可以很容易地与你的门户交互,从而增加你的 extranet 的用户量。

    针对 intranet 的特性

    除了上面所提及的针对 extranet 的特性之外,在部署 intranet 时,还要考虑更多的事项。你应该考虑在 intranet 上使用更多的特性,因为 intranet 用户保持连接的时间一般会比 extranet 用户保持连接的时间要长。这些在设计 intranet 时需要考虑的额外特性包括:

    •数字dashboard:dashboard能够在单个屏幕上显示几个商业过程以及系统的关键状态指示器,让用户能够快速地浏览几个状态,并能够快速地定位问题。数字dashboard使管理人员能够得到组织的完整的总体情况,其䥂?还要考虑更多的事项。你应该考虑在 intranet 上使用更多的特性,因为 intranet 用户保持连接的时间一般会比 extranet 用户保持连接的时间要长。这些在设计 intranet 时需要考虑的额外特性包括:

    •数字dashboard:dashboard能够在单个屏幕上显示几个商业过程以及系统的关键状态指示器,让用户能够快速地浏览几个状态,并能够快速地定位问题。数字dashboard使管理人员能够得到组织的完整的总体情况,奰ull 模式与 Web 站点进行交互的模式,在 pull 模式下,用户必须去查看门户,而在这种新型的 push 模式下,当门户中用户所感兴趣的部分发生变化时他们会收到通知。

    •自服务:门户可以是很多自服务应用程序的一个家园,这里允许客户、职员和其他人做他们自己的事情。

    使用上面列出的这些特性,你就可以创建出一个交互式环境。在你创建的这个交互式环境中,你的客户或其他合作伙伴能够很容易地找到他们需要的东西,并且可以很容易地与你的门户交互,从而增加你的 extranet 的用户量。

    针对 intranet 的特性

    除了上面所提及的针对 extranet 的特性之外,在部署 intranet 时!?用程序的一个家园,这里允许客户、职员和其他人做他们自己的事情。

    使用上面列出的这些特性,你就可以创建出一个交互式环境。在你创建的这个交互式环境中,你的客户或其他合作伙伴能够很容易地找到他们需要的东西,并且可以很容易地与你的门户交互,从而增加你的 extranet 的用户量。

    针对 intranet 的特性

    除了上面所提及的针对 extranet 的特性之外,在部署 intranet 时,还要考虑更多的事项。你应该考虑在 intranet 上使用更多的特性,因为 intranet 用户保持连接的时间一般会比 extranet 用户保持连接的时间要长。这些在设计 intranet 时需要考虑的额外特性包括:

    •数字dashboard:dashboard能够在单个屏幕上显示几个商业过程以及系统的关键状态指示器,让用户能够快速地浏览几个状态,并能够快速地定位问题。数字dashboard使管理人员能够得到组织的完整的总体情况,其䥂?还要考虑更多的事项。你应该考虑在 intranet 上使用更多的特性,因为 intranet 用户保持连接的时间一般会比 extranet 用户保持连接的时间要长。这些在设计 intranet 时需要考虑的额外特性包括:

    •数字dashboard:dashboard能够在单个屏幕上显示几个商业过程以及系统的关键状态指示器,让用户能够快速地浏览几个状态,并能够快速地定位问题。数字dashboard使管理人员能够得到组织的完整的总体情况,其中包括组织中不能降低到一组关键性能指示器的部分。

    •个性化:所谓个性化是指组(group)或单个用户能够定制信息显示的方式。能够将一个组所关心的信息过滤出来,以及能够更改信息在屏幕中的显示位置,是商业门户创建友好用户界面的一个重要方式。

    •知识管理:你的职员掌握着你的组织中大部分的信息。门户为职员通过他们的经验所积累的信息提供丵?中包括组织中不能降低到一组关键性能指示器的部分。

    •个性化:所谓个性化是指组(group)或单个用户能够定制信息显示的方式。能够将一个组所关心的信息过滤出来,以及能够更改信息在屏幕中的显示位置,是商业门户创建友好用户界面的一个重要方式。

    •知识管理:你的职员掌握着你的组织中大部分的信息。门户为职员通过他们的经验所积累的信息提供了䥸?包括组织中不能降低到一组关键性能指示器的部分。

    •个性化:所谓个性化是指组(group)或单个用户能够定制信息显示的方式。能够将一个组所关心的信息过滤出来,以及能够更改信息在屏幕中的显示位置,是商业门户创建友好用户界面的一个重要方式。

    •知识管理:你的职员掌握着你的组织中大部分的信息。门户为职员通过他们的经验所积累的信息提侥9??䥸?包括组织中不能降低到一组关键性能指示器的部分。

    •个性化:所谓个性化是指组(group)或单个用户能够定制信息显示的方式。能够将一个组所关心的信息过滤出来,以及能够更改信息在屏幕中的显示位置,是商业门户创建友好用户界面的一个重要方式。

    •知识管理:你的职员掌握着你的组织中大部分的信息。门户为职员通过他们的经验所积累的信息提供了一个存储仓库。门户有助于扩大组织已经拥有的信息的有效性或优势。

    •协作:一些门户提供一些对于促进更好的协作来说非常必要的工具。这些工具可能包括:显示一些信息,以帮助用户确认他们的同事目前是否在工作岗位上,或者显示一些列表帮助职员来组织任务、事件或通知。

    •分布式控制:很多 IT 组织所面临的一个挑战是维护他们现有的 intranet 系统。逥B了一个存储仓库。门户有助于扩大组织已经拥有的信息的有效性或优势。

    •协作:一些门户提供一些对于促进更好的协作来说非常必要的工具。这些工具可能包括:显示一些信息,以帮助用户确认他们的同事目前是否在工作岗位上,或者显示一些列表帮助职员来组织任务、事件或通知。

    •分布式控制:很多 IT 组织所面临的一个挑战是维护他们现有的 intranet 系统。通过䂸?个存储仓库。门户有助于扩大组织已经拥有的信息的有效性或优势。

    •协作:一些门户提供一些对于促进更好的协作来说非常必要的工具。这些工具可能包括:显示一些信息,以帮助用户确认他们的同事目前是否在工作岗位上,或者显示一些列表帮助职员来组织任务、事件或通知。

    •分布式控制:很多 IT 组织所面临的一个挑战是维护他们现有的 intranet 系统。鹁过䂸?个存储仓库。门户有助于扩大组织已经拥有的信息的有效性或优势。

    •协作:一些门户提供一些对于促进更好的协作来说非常必要的工具。这些工具可能包括:显示一些信息,以帮助用户确认他们的同事目前是否在工作岗位上,或者显示一些列表帮助职员来组织任务、事件或通知。

    •分布式控制:很多 IT 组织所面临的一个挑战是维护他们现有的 intranet 系统。通过一个内容管理系统实现的分布式控制允许单个拥有者管理门户中与他相关的内容。

    定义商业门户

    只知道什么是门户对于你定义商业门户应该是什么样子并没有帮助。要定义商业门户,你必须评估你的组织所面临的商业问题,并评估如何制定一个门户策略来解决这些问题。这样,你可以选择实现你需要的特性来解决那个商业问题。

    建立商业门户站点的朥8??过一个内容管理系统实现的分布式控制允许单个拥有者管理门户中与他相关的内容。

    定义商业门户

    只知道什么是门户对于你定义商业门户应该是什么样子并没有帮助。要定义商业门户,你必须评估你的组织所面临的商业问题,并评估如何制定一个门户策略来解决这些问题。这样,你可以选择实现你需要的特性来解决那个商业问题。

    建立商业门户站点的最大皥8??个内容管理系统实现的分布式控制允许单个拥有者管理门户中与他相关的内容。

    定义商业门户

    只知道什么是门户对于你定义商业门户应该是什么样子并没有帮助。要定义商业门户,你必须评估你的组织所面临的商业问题,并评估如何制定一个门户策略来解决这些问题。这样,你可以选择实现你需要的特性来解决那个商业问题。

    建立商业门户站点的朰大皥8??个内容管理系统实现的分布式控制允许单个拥有者管理门户中与他相关的内容。

    定义商业门户

    只知道什么是门户对于你定义商业门户应该是什么样子并没有帮助。要定义商业门户,你必须评估你的组织所面临的商业问题,并评估如何制定一个门户策略来解决这些问题。这样,你可以选择实现你需要的特性来解决那个商业问题。

    建立商业门户站点的最大的一个挑战是创建的门户项目既要足够大,大到能够提供用户所关系的所有信息;另一方面又要足够小,小到能够在合理的比较短时间内可以对外使用。门户能够证明它们的价值,但是只有在它们具有一个目标商业问题需要解决,并且具有一个适当的整体架构时,这种价值才能得到真正的体现。

    在定义门户时,首先要确保你必须确定有几个项目或措施能够从你?大的一个挑战是创建的门户项目既要足够大,大到能够提供用户所关系的所有信息;另一方面又要足够小,小到能够在合理的比较短时间内可以对外使用。门户能够证明它们的价值,但是只有在它们具有一个目标商业问题需要解决,并且具有一个适当的整体架构时,这种价值才能得到真正的体现。

    在定义门户时,首先要确保你必须确定有几个项目或措施能够从你定义䤸?个挑战是创建的门户项目既要足够大,大到能够提供用户所关系的所有信息;另一方面又要足够小,小到能够在合理的比较短时间内可以对外使用。门户能够证明它们的价值,但是只有在它们具有一个目标商业问题需要解决,并且具有一个适当的整体架构时,这种价值才能得到真正的体现。

    在定义门户时,首先要确保你必须确定有几个项目或措施能够从你定义䤸?个挑战是创建的门户项目既要足够大,大到能够提供用户所关系的所有信息;另一方面又要足够小,小到能够在合理的比较短时间内可以对外使用。门户能够证明它们的价值,但是只有在它们具有一个目标商业问题需要解决,并且具有一个适当的整体架构时,这种价值才能得到真正的体现。

    在定义门户时,首先要确保你必须确定有几个项目或措施能够从你定义的门户中获益。然后要制定一个计划,计划如何通过过程修订和自动化支持的来解决这些问题。第二,要确保你可以在一段时间内将这个门户扩展为更大的、面向整个企业的解决方案。

  • MySQL查询优化技术之使用索引

    2007-02-07 14:50:06

    MySQL查询优化技术系列讲座之使用索引
    索引是提高查询速度的最重要的工具。当然还有其它的一些技术可供使用,但是一般来说引起最大性能差异的都是索引的正确使用。在MySQL邮件列表中,人们经常询问那些让查询运行得更快的方法。在大多数情况下,我们应该怀疑数据表上有没有索引,并且通常在添加索引之后立即解决了问题。当然,并不总是这样简单就可以解决问题的,因为优化技术本来就并非总是简单的。然而,如果没有使用索引,在很多情况下,你试图使用其它的方法来提高性能都是在浪费时间。首先使用索引来获取最大的性能提高,接着再看其它的技术是否有用。

      这一部分讲述了索引是什么以及索引是怎么样提高查询性能的。它还讨论了在某些环境中索引可能降低性能,并为你明智地选择数据表的索引提供了一些指导方针。在下一部分中我们将讨论MySQL查询优化器,它试图找到执行查询的效率最高的方法。了解一些优化器的知识,作为对如何建立索引的补充,对我们是有好处的,因为这样你才能更好地利用自己所建立的索引。某些编写查询的方法实际上让索引不起作用,在一般情况下你应该避免这种情形的发生。

      索引的优点

      让我们开始了解索引是如何工作的,首先有一个不带索引的数据表。不带索引的表仅仅是一个无序的数据行集合。例如,图1显示的ad表就是不带索引的表,因此如果需要查找某个特定的公司,就必须检查表中的每个数据行看它是否与目标值相匹配。这会导致一次完全的数据表扫描,这个过程会很慢,如果这个表很大,但是只包含少量的符合条件的记录,那么效率会非常低。

    MySQL查询优化技术系列讲座之使用索引
    图1:无索引的ad表

      图2是同样的一张数据表,但是增加了对ad表的company_num数据列的索引。这个索引包含了ad表中的每个数据行的条目,但是索引的条目是按照company_num值排序的。现在,我们不是逐行查看以搜寻匹配的数据项,而是使用索引。假设我们查找公司13的所有数据行。我们开始扫描索引并找到了该公司的三个值。接着我们碰到了公司14的索引值,它比我们正在搜寻的值大。索引值是排过序的,因此当我们读取了包含14的索引记录的时候,我们就知道再也不会有更多的匹配记录,可以结束查询操作了。因此使用索引获得的功效是:我们找到了匹配的数据行在哪儿终止,并能够忽略其它的数据行。另一个功效来自使用定位算法查找第一条匹配的条目,而不需要从索引头开始执行线性扫描(例如,二分搜索就比线性扫描要快一些)。通过使用这种方法,我们可以快速地定位第一个匹配的值,节省了大量的搜索时间。数据库使用了多种技术来快速地定位索引值,但是在本文中我们不关心这些技术。重点是它们能够实现,并且索引是个好东西。

    MySQL查询优化技术系列讲座之使用索引
    图2:索引后的ad表

      你可能要问,我们为什么不对数据行进行排序从而省掉索引?这样不是也能实现同样的搜索速度的改善吗?是的,如果表只有一个索引,这样做也可能达到相同的效果。但是你可能添加第二个索引,那么就无法一次使用两种不同方法对数据行进行排序了(例如,你可能希望在顾客名称上建立一个索引,在顾客ID号或电话号码上建立另外一个索引)。把与数据行相分离的条目作为索引解决了这个问题,允许我们创建多个索引。此外,索引中的行一般也比数据行短一些。当你插入或删除新的值的时候,移动较短的索引值比移动较长数据行的排序次序更加容易。

      不同的MySQL存储引擎的索引实现的具体细节信息是不同的。例如,对于MyISAM数据表,该表的数据行保存在一个数据文件中,索引值保存在索引文件中。一个数据表上可能有多个索引,但是它们都被存储在同一个索引文件中。索引文件中的每个索引都包含一个排序的键记录(它用于快速地访问数据文件)数组。

      与此形成对照的是,BDB和InnoDB存储引擎没有使用这种方法来分离数据行和索引值,尽管它们也把索引作为排序后的值集合进行操作。在默认情况下,BDB引擎使用单个文件存储数据和索引值。InnoDB使用单个数据表空间(tablespace),在表空间中管理所有InnoDB表的数据和索引存储。我们可以把InnoDB配置为每个表都在自己的表空间中创建,但是即使是这样,数据表的数据和索引也存储在同一个表空间文件中。
    前面的讨论描述了单个表查询环境下的索引的优点,在这种情况下,通过减少对整个表的扫描,使用索引明显地提高了搜索的速度。当你运行涉及多表联结(jion)查询的时候,索引的价值就更高了。在单表查询中,你需要在每个数据列上检查的值的数量是表中数据行的数量。在多表查询中,这个数量可能大幅度上升,因为这个数量是这些表中数据行的数量所产生的。

      假设你拥有三个未索引的表t1、t2和t3,每个表都分别包含数据列i1、i2和i3,并且每个表都包含了1000条数据行,其序号从1到1000。查找某些值匹配的数据行组合的查询可能如下所示:

    SELECT t1.i1, t2.i2, t3.i3
    FROM t1, t2, t3
    WHERE t1.i1 = t2.i2 AND t2.i1 = t3.i3;


      这个查询的结果应该是1000行,每个数据行包含三个相等的值。如果在没有索引的情况下处理这个查询,那么如果我们不对这些表进行全部地扫描,我们是没有办法知道哪些数据行含有哪些值的。因此你必须尝试所有的组合来查找符合WHERE条件的记录。可能的组合的数量是1000 x 1000 x 1000(10亿!),它是匹配记录的数量的一百万倍。这就浪费了大量的工作。这个例子显示,如果没有使用索引,随着表的记录不断增长,处理这些表的联结所花费的时间增长得更快,导致性能很差。我们可以通过索引这些数据表来显著地提高速度,因为索引让查询采用如下所示的方式来处理:

      1.选择表t1中的第一行并查看该数据行的值。

      2.使用表t2上的索引,直接定位到与t1的值匹配的数据行。类似地,使用表t3上的索引,直接定位到与表t2的值匹配的数据行。

      3.处理表t1的下一行并重复前面的过程。执行这样的操作直到t1中的所有数据行都被检查过。

      在这种情况下,我们仍然对表t1执行了完整的扫描,但是我们可以在t2和t3上执行索引查找,从这些表中直接地获取数据行。理论上采用这种方式运行上面的查询会快一百万倍。当然这个例子是为了得出结论来人为建立的。然而,它解决的问题却是现实的,给没有索引的表添加索引通常会获得惊人的性能提高。

      MySQL有几种使用索引的方式:

      · 如上所述,索引被用于提高WHERE条件的数据行匹配或者执行联结操作时匹配其它表的数据行的搜索速度。

      · 对于使用了MIN()或MAX()函数的查询,索引数据列中最小或最大值可以很快地找到,不用检查每个数据行。

      · MySQL利用索引来快速地执行ORDER BY和GROUP BY语句的排序和分组操作。

      · 有时候MySQL会利用索引来读取查询得到的所有信息。假设你选择了MyISAM表中的被索引的数值列,那么就不需要从该数据表中选择其它的数据列。在这种情况下,MySQL从索引文件中读取索引值,它所得到的值与读取数据文件得到的值是相同的。没有必要两次读取相同的值,因此没有必要考虑数据文件。

    索引的代价

      一般来说,如果MySQL能够找到方法,利用索引来更快地处理查询,它就会这样做。这意味着,对于大多数情况,如果你没有对表进行索引,就会使性能受到损害。这就是我所描绘的索引优点的美景。但是它有缺点吗?有的,它在时间和空间上都有开销。在实践中,索引的优点的价值一般会超过这些缺点,但是你也应该知道到底有一些什么缺点。

      首先,索引加快了检索的速度,但是减慢了插入和删除的速度,同时还减慢了更新被索引的数据列中的值的速度。也就是说,索引减慢了大多数涉及写操作的速度。发生这种现象的原因在于写入一条记录的时候不但需要写入数据行,还需要改变所有的索引。数据表带有的索引越多,需要做出的修改就越多,平均性能的降低程度也就越大。在本文的"高效率载入数据"部分中,我们将更细致地了解这些现象并找出处理方法。

      其次,索引会花费磁盘空间,多个索引相应地花费更多的磁盘空间。这可能导致更快地到达数据表的大小限制:

      · 对于MyISAM表,频繁地索引可能引起索引文件比数据文件更快地达到最大限制。

      · 对于BDB表,它把数据和索引值一起存储在同一个文件中,添加索引引起这种表更快地达到最大文件限制。

      · 在InnoDB的共享表空间中分配的所有表都竞争使用相同的公共空间池,因此添加索引会更快地耗尽表空间中的存储。但是,与MyISAM和BDB表使用的文件不同,InnoDB共享表空间并不受操作系统的文件大小限制,因为我们可以把它配置成使用多个文件。只要有额外的磁盘空间,你就可以通过添加新组件来扩展表空间。

      使用单独表空间的InnoDB表与BDB表受到的约束是一样的,因为它的数据和索引值都存储在单个文件中。

      这些要素的实际含义是:如果你不需要使用特殊的索引帮助查询执行得更快,就不要建立索引。

      选择索引

      假设你已经知道了建立索引的语法,但是语法不会告诉你数据表应该如何索引。这要求我们考虑数据表的使用方式。这一部分指导你如何识别出用于索引的备选数据列,以及如何最好地建立索引:

      用于搜索、排序和分组的索引数据列并不仅仅是用于输出显示的。换句话说,用于索引的最好的备选数据列是那些出现在WHERE子句、join子句、ORDER BY或GROUP BY子句中的列。仅仅出现在SELECT关键字后面的输出数据列列表中的数据列不是很好的备选列:

    SELECT
    col_a <- 不是备选列
    FROM
    tbl1 LEFT JOIN tbl2
    ON tbl1.col_b = tbl2.col_c <- 备选列
    WHERE
    col_d = expr; <- 备选列


      当然,显示的数据列与WHERE子句中使用的数据列也可能相同。我们的观点是输出列表中的数据列本质上不是用于索引的很好的备选列。

      Join子句或WHERE子句中类似col1 = col2形式的表达式中的数据列都是特别好的索引备选列。前面显示的查询中的col_b和col_c就是这样的例子。如果MySQL能够利用联结列来优化查询,它一定会通过减少整表扫描来大幅度减少潜在的表-行组合。

      考虑数据列的基数(cardinality)。基数是数据列所包含的不同值的数量。例如,某个数据列包含值1、3、7、4、7、3,那么它的基数就是4。索引的基数相对于数据表行数较高(也就是说,列中包含很多不同的值,重复的值很少)的时候,它的工作效果最好。如果某数据列含有很多不同的年龄,索引会很快地分辨数据行。如果某个数据列用于记录性别(只有"M"和"F"两种值),那么索引的用处就不大。如果值出现的几率几乎相等,那么无论搜索哪个值都可能得到一半的数据行。在这些情况下,最好根本不要使用索引,因为查询优化器发现某个值出现在表的数据行中的百分比很高的时候,它一般会忽略索引,进行全表扫描。惯用的百分比界线是"30%"。现在查询优化器更加复杂,把其它一些因素也考虑进去了,因此这个百分比并不是MySQL决定选择使用扫描还是索引的唯一因素。

      索引较短的值。尽可能地使用较小的数据类型。例如,如果MEDIUMINT足够保存你需要存储的值,就不要使用BIGINT数据列。如果你的值不会长于25个字符,就不要使用CHAR(100)。较小的值通过几个方面改善了索引的处理速度:

      · 较短的值可以更快地进行比较,因此索引的查找速度更快了。

      · 较小的值导致较小的索引,需要更少的磁盘I/O。

      · 使用较短的键值的时候,键缓存中的索引块(block)可以保存更多的键值。MySQL可以在内存中一次保持更多的键,在不需要从磁盘读取额外的索引块的情况下,提高键值定位的可能性。

      对于InnoDB和BDB等使用聚簇索引(clustered index)的存储引擎来说,保持主键(primary key)短小的优势更突出。聚簇索引中数据行和主键值存储在一起(聚簇在一起)。其它的索引都是次级索引;它们存储主键值和次级索引值。次级索引屈从主键值,它们被用于定位数据行。这暗示主键值都被复制到每个次级索引中,因此如果主键值很长,每个次级索引就需要更多的额外空间。

      索引字符串值的前缀(prefixe)。如果你需要索引一个字符串数据列,那么最好在任何适当的情况下都应该指定前缀长度。例如,如果有CHAR(200)数据列,如果前面10个或20个字符都不同,就不要索引整个数据列。索引前面10个或20个字符会节省大量的空间,并且可能使你的查询速度更快。通过索引较短的值,你可以获得那些与比较速度和磁盘I/O节省相关的好处。当然你也需要利用常识。仅仅索引某个数据列的第一个字符串可能用处不大,因为如果这样操作,那么在索引中不会有太多的唯一值。

      你可以索引CHAR、VARCHAR、BINARY、VARBINARY、BLOB和TEXT数据列的前缀。

      使用最左(leftmost)前缀。建立多列复合索引的时候,你实际上建立了MySQL可以使用的多个索引。复合索引可以作为多个索引使用,因为索引中最左边的列集合都可以用于匹配数据行。这种列集合被称为"最左前缀"(它与索引某个列的前缀不同,那种索引把某个列的前面几个字符作为索引值)。

      假设你在表的state、city和zip数据列上建立了复合索引。索引中的数据行按照state/city/zip次序排列,因此它们也会自动地按照state/city和state次序排列。这意味着,即使你在查询中只指定了state值,或者指定state和city值,MySQL也可以使用这个索引。因此,这个索引可以被用于搜索如下所示的数据列组合:

    state, city, zip
    state, city
    state


      MySQL不能利用这个索引来搜索没有包含在最左前缀的内容。例如,如果你按照city或zip来搜索,就不会使用到这个索引。如果你搜索给定的state和具体的ZIP代码(索引的1和3列),该索引也是不能用于这种组合值的,尽管MySQL可以利用索引来查找匹配的state从而缩小搜索的范围。

      不要过多地索引。不要认为"索引越多,性能越高",不要对每个数据列都进行索引。我们在前面提到过,每个额外的索引都会花费更多的磁盘空间,并降低写操作的性能。当你修改表的内容的时候,索引就必须被更新,甚至可能重新整理。如果你的索引很少使用或永不使用,你就没有必要减小表的修改操作的速度。此外,为检索操作生成执行计划的时候,MySQL会考虑索引。建立额外的索引会给查询优化器增加更多的工作量。如果索引太多,有可能(未必)出现MySQL选择最优索引失败的情况。维护自己必须的索引可以帮助查询优化器来避免这类错误。

      如果你考虑给已经索引过的表添加索引,那么就要考虑你将增加的索引是否是已有的多列索引的最左前缀。如果是这样的,不用增加索引,因为已经有了(例如,如果你在state、city和zip上建立了索引,那么没有必要再增加state的索引)。

      让索引类型与你所执行的比较的类型相匹配。在你建立索引的时候,大多数存储引擎会选择它们将使用的索引实现。例如,InnoDB通常使用B树索引。MySQL也使用B树索引,它只在三维数据类型上使用R树索引。但是,MEMORY存储引擎支持散列索引和B树索引,并允许你选择使用哪种索引。为了选择索引类型,需要考虑在索引数据列上将执行的比较操作类型:

      · 对于散列(hash)索引,会在每个数据列值上应用散列函数。生成的结果散列值存储在索引中,并用于执行查询。散列函数实现的算法类似于为不同的输入值生成不同的散列值。使用散列值的好处是散列值比原始值的比较效率更高。散列索引用于执行=或<=>操作等精确匹配的时候速度非常快。但是对于查询一个值的范围效果就非常差了:

    id < 30
    weight BETWEEN 100 AND 150


      · B树索引可以用于高效率地执行精确的或者基于范围(使用操作<、<=、=、>=、>、<>、!=和BETWEEN)的比较。B树索引也可以用于LIKE模式匹配,前提是该模式以文字串而不是通配符开头。

      如果你使用的MEMORY数据表只进行精确值查询,散列索引是很好的选择。这是MEMORY表使用的默认的索引类型,因此你不需要特意指定。如果你希望在MEMORY表上执行基于范围的比较,应该使用B树索引。为了指定这种索引类型,需要给索引定义添加USING BTREE。例如:

    CREATE TABLE lookup
    (
    id INT NOT NULL,
    name CHAR(20),
    PRIMARY KEY USING BTREE (id)
    ) ENGINE = MEMORY;


      如果你希望执行的语句的类型允许,单个MEMORY表可以同时拥有散列索引和B树索引,即使在同一个数据列上。

      有些类型的比较不能使用索引。如果你只是通过把值传递到函数(例如STRCMP())中来执行比较操作,那么对它进行索引就没有价值。服务器必须计算出每个数据行的函数值,它会排除数据列上索引的使用。

      使用慢查询(slow-query)日志来识别执行情况较差的查询。这个日志可以帮助你找出从索引中受益的查询。你可以直接查看日志(它是文本文件),或者使用mysqldumpslow工具来统计它的内容。如果某个给定的查询多次出现在"慢查询"日志中,这就是一个线索,某个查询可能没有优化编写。你可以重新编写它,使它运行得更快。你要记住,在评估"慢查询"日志的时候,"慢"是根据实际时间测定的,在负载较大的服务器上"慢查询"日志中出现的查询会多一些。
  • winrunner 永久注册成功秘笈

    2007-02-05 10:12:57

    100%成功的是这种:
    WinRunner安装心得
    重装系统后,发现用论坛中的方法安装WinRunner7.6还是有问题, 总是提示14天有效。后来发现在C:\Program Files\Common Files\Mercury Interactive有一个License Manager目录,里面有一个lservrc文件。这个文件就是MI工具的注册管理器,把里面的内容替换成下面的内容,就大功 告成了:
    5SFDHYECAW7F8V8ULAEHCVYQSIRG93D29XAQ8KKM4FC#
    # Dynamically installed.
    FF2VLB3CX43FE7GRYIB3W96MVFUFK7UX5IM6DTS2WL4YR8JS4N27M6DWEM9
    # Dynamically installed.
    G8A9EF79GM5Z7C2V6IKBQZ64773ZBDCLMWETBCRHBG57634GFVY9K#
    # Dynamically installed.
    NRDDL7SPI9LDT5PUG8E4MHVA2933VROPSPBIEZ95JLHMH48OC7P8UEFJXNLR GG8W274AHT
    # Dynamically installed.

    不要忘了把自己机器的时间调到2005

    Some handsome boys or preety girls told that " Install the WinRunner7.6 (english version) on my machine.  But I don't have license key."
    ok ,now ,i would share you a mean to solve this kind of problem.

    Step 1: you should open the folder of "C:\Program Files\Common Files\Mercury Interactive\License Manager" ,and you could see a file named "lservrc".
    Step 2: PlZ open the "lservrc" by wordpad(写字板)

    Step 3: This step is most important. Take care,please!
    You should delete all the content of this file ,rewrite the following words:

    5SFDHYECAW7F8V8ULAEHCVYQSIRG93D29XAQ8KKM4FC#
    # Dynamically installed.
    FF2VLB3CX43FE7GRYIB3W96MVFUFK7UX5IM6DTS2WL4YR8JS4N27M6DWEM9
    # Dynamically installed.
    G8A9EF79GM5Z7C2V6IKBQZ64773ZBDCLMWETBCRHBG57634GFVY9K#
    # Dynamically installed.
    NRDDL7SPI9LDT5PUG8E4MHVA2933VROPSPBIEZ95JLHMH48OC7P8UEFJXNLRGG8W274AHT
    # Dynamically installed.

    Ok,you succ! 
    Sorry ,i forget one thing that you should modify your systerm time  to 2005 year!!

    I think ,you could do those better than me.
    Best wishes~~

  • Linux学习 系统FAQ

    2007-01-31 14:11:54

    Linux学习之系统FAQ
    2007-01-26 jeRRy.Lee

    问:Linux系统中,有哪些主要的日志子系统?
    答:连接时间日志由多个程序执行,把记录写到/var/log/wtmp和/var/run/utmp,并由login等程序更新wtmp和utmp文件,使系统管理员能够跟踪谁在何时登录到系统。
    进程统计由系统内核执行。当一个进程终止时,为每个进程向进程统计文件(Pacct或acct)中写一个记录。进程统计的目的是为系统中的基本服务提供命令使用统计。
    错误日志由syslogd(8)执行。各种系统守护进程、用户程序和内核通过syslogd(3)向文件/var/log/messages报告值得注意的事件。另外,有许多Unix程序创建日志。像HTTP和FTP这样提供网络服务的服务器也保持详细的日志。 

    问:Linux下tar和rsync区别?
    答:tar命令用来建立最初的副本,rsync命令则是用来获取最后一个副本建立以来所发生的变更。在不存在任何目标文件时,tar比rsync要快。如果两个文件系统差异很小,则rsync比tar快许多。

    问:Linux有哪些常用的系统维护文件?
    答:(1)/etc/hosts:主机名字解析文件,提供主机名和IP地址的对应。
    (2)/etc/passwd:包含有口令文件、登录用户名、加密口令、用户ID、组ID、初始工作目录和Shell路径等信息。
    (3)/etc/grofup:包含有组名、加密密码、组ID及所有属于该组的用户的信息等。
    (4)/etc/profile:可以设置通用环境变量。
    (5)/etc/inittab:内部初始化之后,系统将启动/etc/init这个Deamon进程,使 /etc/init进程取得引导序列是控制权。

    问:如果/var目录空间不够用,可以移到新的硬盘空间中吗?
    答:当然可以,但是要注意两点:
    1.不是简单的从/ var目录下拷贝资料,而是要用tarzcvf 和zxvf命令打包和解包到新的挂载点,这样才能保证/var的链接文件不会受破坏。
    2.从紧急启动软盘引导启动,如果没有准备就用Linux 安装光盘用 rescue模式进入系统,把/etc/fstab这个文件中的/var分区修改成现在的挂载点。最后退出重新启动,使挂载生效。

    问:我的Linux每次启动直接进入图形界面,有什么方法可以直接进入终端吗?
    答:修改/ e t c / i n i t t a b 文件,找到“i b:X :initdefault”,其中X表示启动方式,改为3表示终端,改为5表示图形。

    问:Linux下怎么实现分卷压缩?
    答:用tar命令每卷50M分卷压缩,命令如下:
    # tar zcvfp – mytarfilr.tar.gz | split –d –b 50m

    问:如何用USB盘启动Linux系统?
    答:首先查看主版是否支持从USB盘启动,启动时,需要到BIOS下设置从USB设备启动。进入BIOS的“AdvancedBIOS Features”下的“first boot device”选项,就有usb-fdd、 usb-hdd、usb-zip和usb-cdrom等USB设备的启动选项了,然后制作一张Linux启动盘。将 boot.img保存在根分区下,运行命令:“dmesg”,查看USB盘设备号。一般是sda。
    运行如下命令:
    # dd if=/boot.img of = /dev/sda
    如果显示如下,则表示制作成功。
    2880 +0 records in 
    2880 +0 records out

    问:我在应用一些特别消耗虚拟内存的程序(如g++)时,发现swap空间已经用完,如何临时增加swap空间呢?
    答:如果是临时增加swap空间,可以使用swap文件来实现。如果增加128MB的swap空间,可以使用以下命令来实现:
    # lsof –i :22
    COMMAND PID USER FD TYPE DEVICE SIZENODE NAME
    sshd 1826 root 3u IPv6 2770 TCP *:ssh(LISTEN)
    # dd if=/dev/zero of=/swap bs=1024 count=131070
    # mkswap /swap
    Setting up swapspace version 1,size = 134209 kB
    # swapo
    swapoff swapon
    其中,count参数是swap文件的大小,of参数是swap文件的名称。请注意,在存放swap文件的分区上要有相应的空间,如上例中“/”分区至少要有128MB的可见才行。使用完毕后,可以用以下命令解释这128MB的swap分区:
    # swapoff /swap
    # rm /swap 

    问:如何防止以外或别人故意修改文件?
    答:对系统中的一些关键文件和个人重要资料,可以通过文件权限来保护,例如将文件属性设为600。另外,如果Linux使用的是ext2或ext3文件系统,还可以使用“chattr”命令,给文件加上i属性,即使root用户也不能直接修改或删除这类文件,以有效防止意外修改情况的发生。具体命令如下:
    # chattr +i passwd
    去除i属性使用如下命令:
    # chattr –i passwd

    问:我在Windows下不小心把swap分区格式化了,请问有什么命令可以恢复?
    答:使用mkswap命令建立swap分区,再使用swapon命令启用swap分区即可。关于命令的使用,举例如下:
    #mkswap /dev/sda7
    # swapon /dev/sda7

     

    1.KDE是什么,有什么特点? 
    KDE项目在1996年10月发起的,其目的是在X-Window上建立一个完整易用的桌面环境。KDE现在除了拥有KFM(类似于IE4.0)、KPresenter(类似PowerPoint)、KIllustrator(类似CorelDraw或 Illustrator)等重量级软件,还有体贴用户的GUI配置软件可以帮助用户配置Unix/Linux,使其深受使用者欢迎。 
    但由于KDE是基于由TrollTech公司开发的Qt程序库的,所以也受到了许多批评。虽然Qt本身作为一基于C++的跨平台开发工具是非常优秀,但可惜的是它不是自由软件。Qt的License允许任何人使用Qt编写免费软件及免费拷贝给其他用户使用,但如果利用Qt编写非免费软件则需要购买他们的License。更重要的是任何人都不可以随意修改Qt源代码。如果TrollTech公司更改Qt License、公司倒闭或给人收购等都会令KDE前功尽弃。 

    2.GNOME是什么,有什么特点? 
    1997年8 月,为了克服KDE所遇到的QT许可协议和单一C++依赖的困难,以墨西哥的Miguel de Icaza为首的250程序员就开始了一个新项目,完全从头开始,这就是GNOME。 
    经过14个月的共同努力,终于完成了这个工程。现在GNOME已得到了占Linux 市场份额最大发行商Red Hat 的支持,拥有了大量应用软件,包括文字处理软件Go,电子表格软件Gnumeric,日历程序GNOMEcal,堪与PhotoShop 媲美的图形图像处理软件Gimp 等。 
    现在GNOME与KDE晌 肆酱缶赫 笥 亟 沟肔inux更加易于使用。 

    3.在Linux下如何播放CD? 
    当你成功地在Linux环境下驱动了你的声卡之后,你就可以通过使用X-window中的一个CD播放器来播放CD了,它在多媒体工具中。界面如下图所示: 

    4.能不能在Linux下播放VCD? 
    你可以使用MpegTV Player 1.0这个Linux下的VCD播放器实现你的愿望。  安装MpegTVPlayer不需要特殊的硬件设备,只需运行X Window即可使用。 
    它模仿了录像机的播放控制键,如 : 播放、快进、快退、停止、定格、搜索、音量调节、静声、音量平衡、循环播放、画面放大缩小、自动播放等。播放过程会显示整个节目要用时间、已用时间。 
    你可以到下面站点下载MpegTV Play:
    http://www.mpegtv.com。 

    5.有没有Linux下MP3播放工具? 
    Linux下有很多MP3的播放工具,其中mpg123 应该是最经典的一种,它可以在控制台使用。许多图形界面的mp3播放软体都是利用这个程式来 播放mp3的。下面是它的一些基本的参数介绍: 
    -a device 设定所使用的音效装置,预设为/dev/audio或/dev/dsp 
    -@ file 从档案file中读取要播放的mp3档案名称列表 
    -z 随机播放 
    如果只是要单纯地播放一两首歌,或是要播放一整个目录下的歌,利用mpg123倒是个不错的选择,若是要复杂一点的功能如选择多目录内的特定mp3档案,那可能需要写个scrīpt来执行。 

    6.如何在Linux下浏览网页? 
    想要浏览网页,需要满足两个条件: 
    1) 你的电脑已经连上了Internet; 
    2) 使用浏览器软件来实现浏览;在Linux下有两种常用的浏览器: 
    控制台下可以使用lynx; 
    X-window中可以使用netscape的Linux版本; 
    它们的使用方法与在Windows中的浏览器基本类似。 

    7.如何在Linux下收发E-Mail 
    在Linux下你可以使用mail、pine在控制台命令行下收发E-Mail,在X-window中可以使用Kmail来收来E-Mail。 

    8.能不能让Linux显示中文呢? 
    用的英文版的Linux,你可以安装中文模拟终端chdrv使Linux能在命令行状态下显示和汉字。chdrv启动后在tty7(用Ctrl+Alt+F7)建立一个虚拟终端,用户在tty7上登录后就可以使用它提供的中文支持。 
    ftp://ftp.ihep.ac.cn/pub/chinese/system获取已编译的chdrvbin-0.23.gb.tar.gz文件和字体文件chdrvfont.tar.gz,然后执行: 
    tar xvzf chdrvbin-0.23.gb.tar.gz 
    mv chdrvfont.tar.gz chdrv-0.23.gb/ 
    cd chdrv-0.23.gb 进入此目录 
    ./installbin 运行安装文件 

    9.有没有Linux下的中文输入法? 
    如果你使用的Linux是中文版,就无需另外安装了。如果你使用英文版的Linux的话,你可以安装chinput来实现中文输入。你可以到下面网址下载: 
    http://www.turbolinux.com.cn/~justiny/download/chinput-2.1.tar.gz 
    1) 下载后,解开该文件:tar -xvzf chinput-2.1.tar.gz 
    2) 解开后,进入 Chinput-2.1/src 里,修改 chinput.c文件,查找 GBK字样,将其改为 GB2312。再查找system("/usr/bin/zwincontrol &");语句,将其取消(在前面加上 // ); 
    3) 修改完后,执行make命令编译; 
    4)编译完成后,就可以在X-Window环境下使用了。 

    10.除了VI,还有其他文字编辑软件吗? 
    Vi是UNIX/Linux环境下最通用的一种编辑软件,但是它不是很友好。如果你对它不满意的话还可以尝试其它一些如emacs、joe等编辑工具。 
    笔者极力推荐emacs,这是自由软件基金FSF之父stallman的杰作,也是GNU项目第一个重磅炸弹,的确十分优秀,值得一试。不过最大缺点就是太复杂。 

    11.Linux下有没有像office一样的软件? 
    有,StarOffice就是基于Linux的一个办公软件的套件,它是一个商业软件包,其中包括字处理、电子表格、幻灯片制作等MS Office软件包含有的软件。对于个人用户还提供一个免费的版本。用户可以到以下站点下载: 
    http://studio.linux.org.cn。 
    StarOffice Office Suite for Linux是为非商业用户准备的,商业用户要使用StarOffice Office 则需要许可证。 
    ftp://ftp.gwdg.de/pub/linux/staroffice 

    12.Linux下有没有像photoshop一样的软件? 
    有,Linux下有一款十分著名的平面图形处理软件—GIMP。它的界面比Photoshop更简洁,启动时只有一个工具栏,看起来和Photoshop差不多.主菜单在用鼠标右键单击打开的图象时才弹出,像Channels,layers,brushes......一应俱全,外观也和Photoshop极为相似,用不着花时间去适应了. .... 
    这还不算特点.比Photoshop优越的地方也不少:如支持的图象格式极多,支持Texture Brush,像Painter那样刷出底纹图案来. Filter也更多了,比如有一种叫Alien Map的,把RGB三色进行sin,cos运算,达到特殊效果,真是奇思妙想.Undo功能也不错,我试了一下,至少支持5次undo.还有更令人欣喜的,就是对Web的支持,可以很轻松的做出定制的箭头,按钮,背景图案等,比Photoimpact更强大。 
    如果你离不开PHOTOSHOP,又付不起ADOBE掌柜的帐,又自认为玩艺术的人不用D版,试试GIMP吧! 

    13.能在Linux下玩游戏吗? 
    现在已经有For Linux的游戏了,除了大名鼎鼎的Doom之外,还用一些像acm—空战模拟游戏、xdemineur—挖地雷、xjewel—俄罗斯方块、xboard—国际象棋、xboing—弹珠台游戏。而且现在越来越多,为你在枯燥的学习中添上一丝乐趣。 

    14.在Linux下有C语言吗?其它语言呢? 
    在Linux下有自由软件基金FSF创建的重磅炸弹-GCC,它是一个强大的编译器,目前堪称是全世界效率最高的C/C++编译器,现在已经广泛应用在各种UNIX环境下。虽然,现在还没有C语言的集成开发环境,但这丝毫不影响GCC的流行,这也许就是自由的力量吧! 
    同时,Linux下也提供Fortran、Pascal、Perl、Cobol等多种语言的支持。 

    15.Linux下有哪些数据库软件? 
    Linux下有许多种数据库软件可供使用,其中有Oracle for Linux、IBM DB2 for Linux、Mysql、mSql、Post等。

  • 如何从用户的角度来测试Web应用软件

    2007-01-29 13:51:05

    如何从用户的角度来测试Web应用软件

       我并不是一个Web开发方面的大师。虽然我从事开发管理工作已经很长时间了,但我的职业生涯是从一个开发人员开始的。当条件允许的时候,我也试着在开发过程之中提供一些帮助,特别是当我认为可以通过我在测试在线客户机—服务器和Web应用软件方面的知识提供一些有用的价值的时候。

      在开发人员完成他们的测试之后,我将会出于两个具有代表性的原因来审查他们的工作。第一,我想要在和客户交流时能够说出应用软件是什么样子和它如何工作。第二,我想要看一看有没有什么显而易见的错误可以在客户看到结果之前得到更正。

      我知道有我在中间会让我的开发人员感觉受到挫折。这种挫折并不是因为我是一个瓶颈,而我通常试图在开发人员告诉我应用软件已经完成的当天之内就开始我的测试工作。真正使他们感到受挫的是他们可以对应用软件进行测试并认为他们已经找到了所有的东西。然而,通常在我开始测试之后的30分钟之内,我就会在一张纸上记录下来我有疑问或是看起来不正常的事情。

      通常这种测试方式也会使我感到受挫。有时,我奇怪开发人员如何能够说应用软件已经完成,而他们所忽视的内容我在几分钟之内就能够找到。然而,一般来说,出现的错误通常是由对测试理念的缺乏所导致。开发人员关注于提供正常工作的应用软件,而我倾向于从一个用户的角度看一看是否能打破它。我还会寻找其中的一些矛盾和直觉性的缺乏,这些反映出了使用者的经验。

    提供正确的应用软件

      测试工作具有一些不同的方面。一方面就是去验证最终的产品达到所认可的要求。测试工作要求测试人员确保所有所要求的功能和特性都已经给出并可用。然后,确保这些功能和特性以所期望的方式工作。这种测试方式并没有错,但是你还需要更进一步。

    试着作为一个用户去打破应用软件

      很多开发人员所欠缺的地方是,他们以他们所期望的用户的反应方式为基础进行测试工作。他们没有进行足够的思考,离开惯常的途径进行测试。例如,比方说你有一个Web应用软件,其中有大量的在线处理过程,如果第一个页面要求输入用户名和密码,那么我一开始就什么值都不输入,然后看一看会发生什么。我能不能进入?有没有错误出现?有些时候是不是屏幕会静止不动?这时,应用软件就应该将其视为一个非法的响应并返回恰当的错误信息。

    用户会向所有可能位置输入任何值

      当我进入应用软件界面时,我会输入各种各样奇怪的值。如果这里需要输入的是字母,那么我就输入一个数字,然后我会输入类似于“(*&%$’的特殊字符。很多次,应用软件都会发生问题,真是让我感到惊异。我对所有的区域都做了相同的测试,如果一个区域包含一个drop-down列表,我就会试着键入一个值。如果某些区域是事先制定的,我就会改变他们。如果一些值是数据库的关键字而不能动,我就会改变他们。我还试着通过在区域中加入页面所允许的足够多的数字或字符,让他们溢出。然后我就会点击可选的按钮和链接看一看会发生什么。

      同样的,我还试着搞乱所有的预制定区域。我总是告诉我的开发人员说,如果你不希望一个区域被改变,那么你就不要允许用户将指针放在上面和键入。我向你保证,如果你将一个区域设置为开放的可以输入,那么就一定会有某些人在某些时候,出于某种原因试图向其中键入数值。

      用户为什么会向一个需要输入数字的区域键入特殊字符呢?问题在于他们或许不会有意去这样做,然而,键入错误去却随时都会发生。如果你向用户给出一个数字区域,那么随着时间的过去,错误的键入就会导致在任何的区域之中输入任何的字符。我认为这样的问题应该现在就找出来,而不是让一个Web应用软件在用户手中出问题。

    用户会尝试逻辑流的所有组合

      除了一些简单的编辑性错误之外,我还会尝试每一个逻辑流的组合。当我看到一个Web页面时,我会尝试每一个超链接看一看结果是什么。开发人员会看着我纳闷为什么用户会这样做。再说一次,问题是他们可能不是有意去这么做,然而,你应该设想每一个逻辑组合都可能会在某个时间被尝试。

    看一看外观

      我着眼的最后一件事就是整体的视觉和感觉。我试图确保屏幕有一个漂亮的外观,漂亮的字体,而且他们是协调一致的。例如,如果你在列表中一些项目的最后放置一个句号,那么他们都应该带有句号,否则就都没有,这取决于你的编辑上的习惯。同样,字体也应该保持一致,如果在一个区域的标题的字体是14,那么他们都应该是这个大小。这样做都是为了使应用软件看起来具有专业性。

    做最坏的准备

      在我所管理的团体之中,开发人员做出了很好的工作,确保他们的应用软件以所指定的方式工作。但在很多情况下,他们没有从一个用户的角度做出足够的测试工作。他们应该关注于确保应用软件的坚固可靠。用户在百分之九十的时间之内,会像你所期望的那样对应用软件进行操作,然而,剩下的百分之十的时间里,他们就会做一些奇怪的事情。当发生这样的事情时,你的应用软件就需要对其妥当并成功地进行处理。你不希望一个很棒的应用软件在用户第一次输入12位数字的社会保障号码而不是9位数字时就垮掉。你要确保进行了测试工作保证你的应用软件如宣传的那样进行工作。还有,尽可能地对意外因素的组合进行多种测试。你需要确保没有任何的错误数据或处理流程致使用户得到任何意料之外的系统信息。

  • 软件测试常识

    2007-01-27 11:43:27

    软件测试常识

                                        软件测试常识

     

        软件开发和使用的历史已经留给了我们很多由于软件缺陷而导致的巨大财力、物力损失的经验教训。这些经验教训迫使我们这些测试工程师们必须采取强有力的检测措施来检测未发现的隐藏的软件缺陷。

        生产软件的最终目的是为了满足客户需求,我们以客户需求作为评判软件质量的标准,认为软件缺陷(Software Bug )的具体含义包括下面几个因素:

    •  软件未达到客户需求的功能和性能;

    •  软件超出客户需求的范围;

    •  软件出现客户需求不能容忍的错误;

    •  软件的使用未能符合客户的习惯和工作环境。

        考虑到设计等方面的因素,我们还可以认为软件缺陷还可以包括软件设计不符合规范,未能在特定的条件(资金、范围等)达到最佳等。可惜的是,我们中的很多人更倾向于把软件缺陷看成运行时出现问题上来,认为软件测试仅限于程序提交之后。

        在目前的国内环境下,我们几乎看不到完整准确的客户需求说明书,加以客户的需求时时在变,追求完美的测试变得不太可能。因此作为一个优异的测试人员,追求软件质量的完美固然是我们的宗旨,但是明确软件测试现实与理想的差距,在软件测试中学会取舍和让步,对软件测试是有百益而无一弊的。

        下面是一些软件测试的常识,对这些常识的理解和运用将有助于我们在进行软件测试时能够更好的把握软件测试的尺度。


    •  测试是不完全的(测试不完全)

        很显然,由于软件需求的不完整性、软件逻辑路径的组合性、输入数据的大量性及结果多样性等因素,哪怕是一个极其简单的程序,要想穷尽所有逻辑路径,所有输入数据和验证所有结果是非常困难的一件事情。我们举一个简单的例子,比如说求两个整数的最大公约数。其输入信息为两个正整数。但是如果我们将整个正整数域的数字进行一番测试的话,从其数目的无限性我们便可证明是这样的测试在实际生活中是行不通的,即便某一天我们能够穷尽该程序,只怕我们乃至我们的子孙都早已作古了。为此作为软件测试,我们一般采用等价类和边界值分析等措施来进行实际的软件测试,寻找最小用例集合成为我们精简测试复杂性的一条必经之道。


    •  测试具有免疫性(软件缺陷免疫性)

        软件缺陷与病毒一样具有可怕的“ 免疫性” ,测试人员对其采用的测试越多,其免疫能力就越强,寻找更多软件缺陷就更加困难。由数学上的概率论我们可以推出这一结论。假设一个50000 行的程序中有500 个软件缺陷并且这些软件错误分布时均匀的,则每100 行可以找到一个软件缺陷。我们假设测试人员用某种方法花在查找软件缺陷的精力为X 小时/100 行。照此推算,软件存在500 个缺陷时,我们查找一个软件缺陷需要X 小时,当软件只存在5 个错误时,我们每查找一个软件缺陷需要100X 小时。实践证明,实际的测试过程比上面的假设更为苛刻,为此我们必须更换不同的测试方式和测试数据。该例子还说明了在软件测试中采用单一的方法不能高效和完全的针对所有软件缺陷,因此软件测试应该尽可能的多采用多种途径进行测试。


    •  测试是“ 泛型概念” (全程测试)

        我一直反对软件测试仅存在于程序完成之后。如果单纯的只将程序设计阶段后的阶段称之为软件测试的话,需求阶段和设计阶段的缺陷产生的放大效应会加大。这非常不利于保证软件质量。需求缺陷、设计缺陷也是软件缺陷,记住“ 软件缺陷具有生育能力” 。软件测试应该跨越整个软件开发流程。需求验证(自检)和设计验证(自检)也可以算作软件测试(建议称为:需求测试和设计测试)的一种。软件测试应该是一个泛型概念,涵盖整个软件生命周期,这样才能确保周期的每个阶段禁得起考验。同时测试本身也需要有第三者进行评估(信息系统审计和软件工程监理),即测试本身也应当被测试,从而确保测试自身的可靠性和高效性。否则自身不正,难以服人。

        另外还需指出的是软件测试是提高软件产品质量的必要条件而非充分条件,软件测试是提高产品质量最直接、最快捷的手段,但决不是一个根本手段。


    •  80-20 原则

        80% 的软件缺陷常常生存在软件20% 的空间里。这个原则告诉我们,如果你想使软件测试有效地话,记住常常光临其高危多发“ 地段” 。在那里发现软件缺陷的可能性会大的多。这一原则对于软件测试人员提高测试效率及缺陷发现率有着重大的意义。聪明的测试人员会根据这个原则很快找出较多的缺陷而愚蠢的测试人员却仍在漫无目的地到处搜寻。

        80-20 原则的另外一种情况是,我们在系统分析、系统设计、系统实现阶段的复审,测试工作中能够发现和避免80% 的软件缺陷,此后的系统测试能够帮助我们找出剩余缺陷中的80% ,最后的5% 的软件缺陷可能只有在系统交付使用后用户经过大范围、长时间使用后才会曝露出来。因为软件测试只能够保证尽可能多地发现软件缺陷,却无法保证能够发现所有的软件缺陷。

        80-20 原则还能反映到软件测试的自动化方面上来,实践证明80% 的软件缺陷可以借助人工测试而发现,20% 的软件缺陷可以借助自动化测试能够得以发现。由于这二者间具有交叉的部分,因此尚有5% 左右的软件缺陷需要通过其他方式进行发现和修正。


    •  为效益而测试

        为什么我们要实施软件测试,是为了提高项目的质量效益最终以提高项目的总体效益。为此我们不难得出我们在实施软件测试应该掌握的度。软件测试应该在软件测试成本和软件质量效益两者间找到一个平衡点。这个平衡点就是我们在实施软件测试时应该遵守的度。单方面的追求都必然损害软件测试存在的价值和意义。一般说来,在软件测试中我们应该尽量地保持软件测试简单性,切勿将软件测试过度复杂化,拿物理学家爱因斯坦的话说就是:Keep it simple but not too simple 。


    •  缺陷的必然性

        软件测试中,由于错误的关联性,并不是所有的软件缺陷都能够得以修复。某些软件缺陷虽然能够得以修复但在修复的过程中我们会难免引入新的软件缺陷。很多软件缺陷之间是相互矛盾的,一个矛盾的消失必然会引发另外一个矛盾的产生。比如我们在解决通用性的缺陷后往往会带来执行效率上的缺陷。更何况在缺陷的修复过程中,我们常常还会受时间、成本等方面的限制因此无法有效、完整地修复所有的软件缺陷。因此评估软件缺陷的重要度、影响范围,选择一个折中的方案或是从非软件的因素(比如提升硬件性能)考虑软件缺陷成为我们在面对软件缺陷时一个必须直面的事实。


    •  软件测试必须有预期结果

        没有预期结果的测试是不可理喻的。软件缺陷是经过对比而得出来的。这正如没有标准无法进行度量一样。如果我们事先不知道或是无法肯定预期的结果,我们必然无法了解测试正确性。这很容易然人感觉如盲人摸象一般,不少测试人员常常凭借自身的感觉去评判软件缺陷的发生,其结果往往是把似是而非的东西作为正确的结果来判断,因此常常出现误测的现象。


    •  软件测试的意义- 事后分析

        软件测试的目的单单是发现缺陷这么简单吗?如果是“ 是” 的话,我敢保证,类似的软件缺陷在下一次新项目的软件测试中还会发生。古语说得好,“ 不知道历史的人必然会重蹈覆辙” 。没有对软件测试结果进行认真的分析,我们就无法了解缺陷发生的原因和应对措施,结果是我们不得不耗费的大量的人力和物力来再次查找软件缺陷。很可惜,目前大多测试团队都没有意识到这一点,测试报告中缺乏测试结果分析这一环节。


    结论:    软件测试是一个需要“ 自觉” 的过程,作为一个测试人员,遇事沉着,把持尺度,从根本上应对软件测试有着正确的认识,希望本文对读者对软件测试的认识有所帮助。

  • Web功能测试工具-MAXQ应用介绍

    2007-01-26 14:41:54

    前两天在论坛上看到,感觉不错,特此整理了下,发布出来
      MAXQ是开源的Web功能测试工具。他的特点:1)简单易学;2)是一个轻量级的Web功能测试工具;3)可以自动录制WebBrowser提交的请求包,并随时回放;4)MAXQ应用了WebProxy代理方式,不直接录制Web的界面,避免在回放时不能识别控件而造成回放停止。
      我们知道就算是商用重量级的工具同样存在不能准确识别控件,这是困扰着GUI自动测试的技术难题.而MAXQ是一个代理Web服务的角色,不直接录制界面,因此不存在界面控件识别问题;MAXQ录制来自前端向服务器发出的业务请求,不是录制前端界面的操作过程;MAXQ的脚本是行命令方式,回放简单快速。
    MAXQ的基本原理:

    安装
    JDK1.4以上;展开MAXQ到预定目录下即可。
    修改配置:修改maxq.properties;指定WEB应用服务器;remote.proxy.host=192.168.3.41;remote.proxy.port=8080
    指定MAXQ代理
    local.proxy.port=8090
    修改Internet配置
    工具->Internet选项->连接->局域网设置->选择为LAN使用代理服务器,地址栏输入localhost,端口选择8090

    启动MAXQ
    MAXQ的bin目录下,运行maxq.bat
    正常时出现下界面



    录制准备
    设置一个新的录制new->standard scrīpt


    开始录制
    选择test->start recording



    Browser操作
    打开IE
    运行http://localhost,显示需要测试WEB应用


    结束录制
    选择test->stop recording贮存脚本file->save
     
    回放录制
    选择file->open(打开脚本)
    选择test->run
     
    分析测试结果
    查看测试结果界面,成功的话显示Test Ran Successfully
     
    注意事项(1)
    web界面测试
     MAXQ不是测试界面的工具,因此web的界面测试还需要人工测试或应用诸如Winrunner、Testcomplete工具自动测试。
    脚本录制
     当功能已经正确的前提下才录制脚本。
    脚本大小
     从业务上划分,通常把一个完整的业务过程作为录制脚本的对象;
    适宜关联业务流程录制;
    不要把不相关的业务录制在同一个脚本中;
    注意事项(2)
    测试检查
     需要另外加测试点检查
  • Web 测试的经验

    2007-01-26 13:52:21

    Web 测试的经验

    作者:jeRRy.Lee  


    1. 功能测试


    1.1.链接测试

       链接是 Web 应用系统的一个主要特征,它是在页面之间切换和指导用户去一些不知道地址的页面的主要手段。链接测试可分为三个方面。首先,测试所有链接是否按指示的那样确实链接到了该链接的页面;其次,测试所链接的页面是否存在;最后,保证 Web 应用系统上没有孤立的页面,所谓孤立页面是指没有链接指向该页面,只有知道正确的 URL 地址才能访问。

       链接测试可以自动进行,现在已经有许多工具可以采用。链接测试必须在集成测试阶段完成,也就是说,在整个 Web 应用系统的所有页面开发完成之后进行链接测试。

    1.2. 表单测试

       当用户给 Web 应用系统管理员提交信息时,就需要使用表单操作,例如用户注册、登陆、信息提交等。在这种情况下,我们必须测试提交操作的完整性,以校验提交给服务器的信息的正确性。例如:用户填写的出生日期与职业是否恰当,填写的所属省份与所在城市是否匹配等。如果使用了默认值,还要检验默认值的正确性。如果表单只能接受指定的某些值,则也要进行测试。例如:只能接受某些字符,测试时可以跳过这些字符,看系统是否会报错。

    1.3.Cookies测试

      Cookies 通常用来存储用户信息和用户在某应用系统的操作,当一个用户使用 Cookies 访问了某一个应用系统时, Web 服务器将发送关于用户的信息,把该信息以 Cookies 的形式存储在客户端计算机上,这可用来创建动态和自定义页面或者存储登陆等信息。

       如果 Web 应用系统使用了 Cookies ,就必须检查 Cookies 是否能正常工作。测试的内容可包括 Cookies 是否起作用,是否按预定的时间进行保存,刷新对 Cookies 有什么影响等。

    1.4.设计语言测试

      Web 设计语言版本的差异可以引起客户端或服务器端严重的问题,例如使用哪种版本的 HTML 等。当在分布式环境中开发时,开发人员都不在一起,这个问题就显得尤为重要。除了 HTML 的版本问题外,不同的脚本语言,例如 Java 、 Javascrīpt 、 ActiveX 、 VBscrīpt 或 Perl 等也要进行验证。

    1.5.数据库测试

       在 Web 应用技术中,数据库起着重要的作用,数据库为 Web 应用系统的管理、运行、查询和实现用户对数据存储的请求等提供空间。在 Web 应用中,最常用的数据库类型是关系型数据库,可以使用 SQL 对信息进行处理。

      在使用了数据库的 Web 应用系统中,一般情况下,可能发生两种错误,分别是数据一致性错误和输出错误。数据一致性错误主要是由于用户提交的表单信息不正确而造成的,而输出错误主要是由于网络速度或程序设计问题等引起的,针对这两种情况,可分别进行测试。

    2. 性能测试

    2.1.连接速度测试

       用户连接到 Web 应用系统的速度根据上网方式的变化而变化,他们或许是电话拨号,或是宽带上网。当下载一个程序时,用户可以等较长的时间,但如果仅仅访问一个页面就不会这样。如果 Web 系统响应时间太长(例如超过 5 秒钟),用户就会因没有耐心等待而离开。
       另外,有些页面有超时的限制,如果响应速度太慢,用户可能还没来得及浏览内容,就需要重新登陆了。而且,连接速度太慢,还可能引起数据丢失,使用户得不到真实的页面。

    2.2.负载测试

       负载测试是为了测量 Web 系统在某一负载级别上的性能,以保证 Web 系统在需求范围内能正常工作。负载级别可以是某个时刻同时访问 Web 系统的用户数量,也可以是在线数据处理的数量。例如: Web 应用系统能允许多少个用户同时在线?如果超过了这个数量,会出现什么现象? Web 应用系统能否处理大量用户对同一个页面的请求?

    2.3.压力测试

      负载测试应该安排在 Web 系统发布以后,在实际的网络环境中进行测试。因为一个企业内部员工,特别是项目组人员总是有限的,而一个 Web 系统能同时处理的请求数量将远远超出这个限度,所以,只有放在 Internet 上,接受负载测试,其结果才是正确可信的。

      进行压力测试是指实际破坏一个 Web 应用系统,测试系统的反映。压力测试是测试系统的限制和故障恢复能力,也就是测试 Web 应用系统会不会崩溃,在什么情况下会崩溃。黑客常常提供错误的数据负载,直到 Web 应用系统崩溃,接着当系统重新启动时获得存取权。

       压力测试的区域包括表单、登陆和其他信息传输页面等。

    3. 可用性测试

    3.1.导航测试

      导航描述了用户在一个页面内操作的方式,在不同的用户接口控制之间,例如按钮、对话框、列表和窗口等;或在不同的连接页面之间。通过考虑下列问题,可以决定一个 Web 应用系统是否易于导航:导航是否直观? Web 系统的主要部分是否可通过主页存取? Web 系统是否需要站点地图、搜索引擎或其他的导航帮助?

       在一个页面上放太多的信息往往起到与预期相反的效果。 Web 应用系统的用户趋向于目的驱动,很快地扫描一个 Web 应用系统,看是否有满足自己需要的信息,如果没有,就会很快地离开。很少有用户愿意花时间去熟悉 Web 应用系统的结构,因此, Web 应用系统导航帮助要尽可能地准确。

       导航的另一个重要方面是 Web 应用系统的页面结构、导航、菜单、连接的风格是否一致。确保用户凭直觉就知道 Web 应用系统里面是否还有内容,内容在什么地方。
    Web 应用系统的层次一旦决定,就要着手测试用户导航功能,让最终用户参与这种测试,效果将更加明显。

    3.2.图形测试

       在 Web 应用系统中,适当的图片和动画既能起到广告宣传的作用,又能起到美化页面的功能。一个 Web 应用系统的图形可以包括图片、动画、边框、颜色、字体、背景、按钮等。图形测试的内容有:

       (1)要确保图形有明确的用途,图片或动画不要胡乱地堆在一起,以免浪费传输时间。 Web 应用系统的图片尺寸要尽量地小,并且要能清楚地说明某件事情,一般都链接到某个具体的页面。
       (2)验证所有页面字体的风格是否一致。
       (3) 背景颜色应该与字体颜色和前景颜色相搭配。
       (4)图片的大小和质量也是一个很重要的因素,一般采用 JPG 或 GIF 压缩。

    3.3.内容测试

       内容测试用来检验 Web 应用系统提供信息的正确性、准确性和相关性。

      信息的正确性是指信息是可靠的还是误传的。例如,在商品价格列表中,错误的价格可能引起财政问题甚至导致法律纠纷;信息的准确性是指是否有语法或拼写错误。这种测试通常使用一些文字处理软件来进行,例如使用 Microsoft Word 的 " 拼音与语法检查 " 功能;信息的相关性是指是否在当前页面可以找到与当前浏览信息相关的信息列表或入口,也就是一般 Web 站点中的所谓 " 相关文章列表 " 。

    3.4.整体界面测试

       整体界面是指整个 Web 应用系统的页面结构设计,是给用户的一个整体感。例如:当用户浏览 Web 应用系统时是否感到舒适,是否凭直觉就知道要找的信息在什么地方?整个 Web 应用系统的设计风格是否一致?

      对整体界面的测试过程,其实是一个对最终用户进行调查的过程。一般 Web 应用系统采取在主页上做一个调查问卷的形式,来得到最终用户的反馈信息。

       对所有的可用性测试来说,都需要有外部人员(与 Web 应用系统开发没有联系或联系很少的人员)的参与,最好是最终用户的参与。

    4. 客户端兼容性测试

    4.1.平台测试

       市场上有很多不同的操作系统类型,最常见的有 Windows 、 Unix 、 Macintosh 、 Linux 等。 Web 应用系统的最终用户究竟使用哪一种操作系统,取决于用户系统的配置。这样,就可能会发生兼容性问题,同一个应用可能在某些操作系统下能正常运行,但在另外的操作系统下可能会运行失败。

       因此,在 Web 系统发布之前,需要在各种操作系统下对 Web 系统进行兼容性测试。

    4.2.浏览器测试

       浏览器是 Web 客户端最核心的构件,来自不同厂商的浏览器对 Java ,、 Javascrīpt 、 ActiveX 、 plug-ins 或不同的 HTML 规格有不同的支持。例如, ActiveX 是 Microsoft 的产品,是为 Internet Explorer 而设计的, Javascrīpt 是 Netscape 的产品, Java 是 Sun 的产品等等。另外,框架和层次结构风格在不同的浏览器中也有不同的显示,甚至根本不显示。不同的浏览器对安全性和 Java 的设置也不一样。

       测试浏览器兼容性的一个方法是创建一个兼容性矩阵。在这个矩阵中,测试不同厂商、不同版本的浏览器对某些构件和设置的适应性。

    5. 安全性测试

    Web 应用系统的安全性测试区域主要有:

       ( 1 )现在的 Web 应用系统基本采用先注册,后登陆的方式。因此,必须测试有效和无效的用户名和密码,要注意到是否大小写敏感,可以试多少次的限制,是否可以不登陆而直接浏览某个页面等。

       ( 2 ) Web 应用系统是否有超时的限制,也就是说,用户登陆后在一定时间内(例如 15 分钟)没有点击任何页面,是否需要重新登陆才能正常使用。

       ( 3 )为了保证 Web 应用系统的安全性,日志文件是至关重要的。需要测试相关信息是否写进了日志文件、是否可追踪。

       ( 4 )当使用了安全套接字时,还要测试加密是否正确,检查信息的完整性。

       ( 5 )服务器端的脚本常常构成安全漏洞,这些漏洞又常常被黑客利用。所以,还要测试没有经过授权,就不能在服务器端放置和编辑脚本的问题。

    6. 总结

       本文从功能、性能、可用性、客户端兼容性、安全性等方面讨论了基于 Web 的系统测试方法。

      基于 Web 的系统测试与传统的软件测试既有相同之处,也有不同的地方,对软件测试提出了新的挑战。基于 Web 的系统测试不但需要检查和验证是否按照设计的要求运行,而且还要评价系统在不同用户的浏览器端的显示是否合适。重要的是,还要从最终用户的角度进行安全性和可用性测试。

  • web应用程序测试方法和测试技术详述

    2007-01-26 13:37:13

    web应用程序测试方法和测试技术详述

    web应用程序测试方法和测试技术详述
                    作者:51testing论坛

    1. 概述
        随着web应用的增多,新的模式解决方案中以web为核心的应用也越来越多,很多公司各种应用的架构都以B/S及web应用为主,但是有关WEB测试方面的内容并没有相应的总结,所以我在这里对web的测试方法和采用的测试技术进行总结,便于内部交流。
        测试方法尽量涵盖web程序的各个方面,测试技术方面在继承传统测试技术的技术上结合web应用的特点。
        相关的测试和实现技术也有着很大的关系,由于本公司使用J2EE体系,也许例子中只有JAVA平台可以使用,.NET平台测试技术暂时不涉及,如果你有请与我联系。

    2. 测试方法
        说明:测试方法的选择取决你的测试策略。
         一般的web测试和以往的应用程序的测试的侧重点不完全相同,基本包括以下几个方面。
     当然圆满的完成测试还要有好的团体和流程等的方方面面的支持,你同样应该对这些方面进行意。
       有些测试方法设计到了流程,哪些应该在你的测试团队建设中建立。
     
    2.1 界面测试

        现在一般人都有使用浏览器浏览网页的经历,用户虽然不是专业人员但是对界面效果的印象是很重要的。如果你注重这方面的测试,那么验证应用程序是否易于使用就非常重要了。很多人认为这是测试中最不重要的部分,但是恰恰相反界面对不懂技术的客户来说那相当关键,慢慢体会你会明的。
    方法上可以根据设计文档,如果够专业的话可以专业美工人员,来确定整体风格页面风格,然后根据这个可以页面人员可以生成静态的HTML,CSS等甚至生成几套不用的方案来讨论,或者交给客户评审,最后形成统一的风格的页面/框架。注意不要靠程序员的美术素养形成你的web风格,那样可能会很糟糕。

    主要包括以下几个方面的内容:
        站点地图和导航条位置、是否合理、是否可以导航等内容布局布局是否合理,滚动条等简介说明说明文字是否合理,位置,是否正确.背景/色调是否正确、美观,是否符合用户需求;
    页面在窗口中的显示是否正确、美观(在调整浏览器窗口大小时,屏幕刷新是否正确)表单样式大小,格式,是否对提交数据进行验证(如果在页面部分进行验证的话)等连接连接的形式,位置,是否易于理解等

    web测试的主要页面元素

    页面元素的容错性列表(如输入框、时间列表或日历)
    页面元素清单(为实现功能,是否将所需要的元素全部都列出来了,如按钮、单选框、复选框、列表框、超连接、输入框等等)
    页面元素的容错性是否存在
    页面元素的容错性是否正确
    页面元素基本功能是否实现(如文字特效、动画特效、按钮、超连接)
    页面元素的外形、摆放位置(如按钮、列表框、核选框、输入框、超连接等)
    页面元素是否显示正确(主要针对文字、图形、签章)
    元素是否显示(元素是否存在)
    页面元素清单(为实现功能,是否将所需要的元素全部都列出来了,如按钮、单选框、复选框、列表框、超连接、输入框等等)
     
    测试技术
       通过页面走查,浏览确定使用的页面是否符合需求。可以结合兼容性测试对不用分辨率下页面显示效果,如果有影响应该交给设计人员提出解决方案。
    可以结合数据定义文档查看表单项的内容,长度等信息。
    对于动态生成的页面最好也能进行浏览查看。如Servelet部分可以结合编码规范,进行代码走查。是否支持中文,如果数据用XML封装要做的工作会多一点等等。
     
    界面测试要素:
      符合标准和规范,灵活性,正确性,直观性,舒适性,实用性,一致性
      1.直观性:
       用户界面是否洁净,不唐突,不拥挤.界面不应该为用户制造障碍.所需功能或者期待的响应应该明显,并在预期出现的地方.
    界面组织和布局合理吗?是否允许用户轻松地从一个功能转到另一个功能?下一步做什么明显吗?任何时刻都可以决定放弃或者退回,退出吗?输入得到承认了吗?菜单或者窗口是否深藏不露?
    有多余功能吗?软件整体抑或局部是否做得太多?是否有太多特性把工作复杂化了?是否感到信息太庞杂?
    如果其他所有努力失败,帮助系统真能帮忙吗?
     
     2.一致性
        快速键和菜单选项.在Windows 中按F1键总是得到帮助信息
    术语和命令.整个软件使用同样的术语吗?特性命名一致吗?例如,Find是否一直叫Find,而不是有时叫Search?
    软件是否一直面向同一级别用户?带有花哨用户界面的趣味贺卡程序不应该显示泄露技术机密的错误提示信息.
    按钮位置和等价的按键.大家是否注意到对话框有OK按钮和Cancle按钮时,OK按钮总是在上方或者左方,而Cancle按钮总是在下方或右方?同样原因,Cancle按钮的等价按键通常是Esc,而选中按钮的等价按钮通常是Enter.保持一致
     
      3.灵活性
       状态跳转.灵活的软件实现同一任务有多种选择方式.
    状态终止和跳过,具有容错处理能力.
    数据输入和输出.用户希望有多种方法输入数据和查看结果.例如,在写字板插入文字可用键盘输入,粘贴,从6种文件格式读入,作为对象插入,或者用鼠标从其他程序拖动.
      4.舒适性
    恰当.软件外观和感觉应该与所做的工作和使用者相符.
    错误处理.程序应该在用户执行严重错误的操作之前提出警告,并允许用户恢复由于错误操作导致丢失的数据.如大家认为undo /redo是当然的.
    性能.快不见得是好事.要让用户看得清程序在做什么,它是有反应的.
     
    2.2 功能测试
     对功能测试是测试中的重点
     主要包括一下几个方面的内容

       连接这个连接和界面测试中的连接不同那里注重的是连接方式和位置,如是图像还是文字放置的位置等,还是其他的方式。这里的连接注重功能。如是否有连接,连接的是否是说明的位置等。
    表单提交应当模拟用户提交,验证是否完成功能,如注册信息,要测试这些程序,需要验证服务器能正确保存这些数据,而且后台运行的程序能正确解释和使用这些信息。还有数据正确性验证,异常处理等,最好结合易用性要求等。B/S结构实现的功能可能主要的就在这里,提交数据,处理数据等如果有固定的操作流程可以考虑自动化测试工具的录制功能,编写可重复使用的脚本代码,可以在测试、回归测试时运行以便减轻测试人员工作量。
        Cookies 验证如果系统使用了cookie,测试人员需要对它们进行检测。如果在 cookies 中保存了注册信息,请确认该 cookie能够正常工作而且已对这些信息已经加密。如果使用 cookie 来统计次数,需要验证次数累计正确。关于cookie的使用可以参考浏览器的帮助信息。如果使用B/S结构cookies中存放的信息更多。功能易用性测试完成了功能测试可以对应用性进行了解,最好听听客户的反映,在可以的情况下对程序进行改进是很有必要的,和客户保持互动对系统满意度也是很有帮助的。
        测试技术功能测试的测试技术可是很多的,我们可以结合实际环境选择使用

        白盒测试技术(White Box Testing) 深入到代码一级的测试,使用这种技术发现问题最早,效果也是最好的。该技术主要的特征是测试对象进入了代码内部,根据开发人员对代码和对程序的熟悉程度,对有需要的部分进行在软件编码阶段,开发人员根据自己对代码的理解和接触所进行的软件测试叫做白盒测试。这一阶段测试以软件开发人员为主,在JAVA平台使用Xunit系列工具进行测试,Xunit测试工具是类一级的测试工具对每一个类和该类的方法进行测试。

        黑盒测试技术(Black Box Testing)黑盒测试的内容主要有以下几个方面,但是主要还是功能部分。主要是覆盖全部的功能,可以结合兼容,性能测试等方面进行,根据软件需求,设计文档,模拟客户场景随系统进行实际的测试,这种测试技术是使用最多的测试技术涵盖了测试的方方面面,可以考虑以下方面
    正确性 (Correctness):计算结果,命名等方面?
    可用性 (Usability):是否可以满足软件的需求说明。
    边界条件 (Boundary Condition)输入部分的边界值,就是使用一般书中说的等价类划分,试试最大最小和非法数据等等.
         性能 (Performance) 正常使用的时间内系统完成一个任务需要的时间,多人同时使用的时候响应时间,在可以接受范围内.J2EE技术实现的系统在性能方面更是需要照顾的,一般原则是3秒以下接受,3-5秒可以接受,5秒以上就影响易用性了. 如果在测试过程中发现性能问题,修复起来是非常艰难的,因为这常常意味着程序的算法不好,结构不好,或者设计有问题。因此在产品开发的开始阶段,就要考虑到软件的性能问题

        压力测试 (Stress)
        多用户情况可以考虑使用压力测试工具,建议将压力和性能测试结合起来进行.如果有负载平衡的话还要在服务器端打开监测工具,查看服务器CPU使用率,内存占用情况,如果有必要可以模拟大量数据输入,对硬盘的影响等等信息.如果有必要的话必须进行性能优化(软硬件都可以).这里的压力测试针对的是某几项功能,.
    错误恢复 (Error Recovery) 错误处理,页面数据验证,包括突然间断电,输入脏数据等.

       安全性测试(Security) 
       这个领域正在研究中,不过防火墙,补丁包.杀毒软件等的就不必说了,不过可以考虑破坏性测试时任意.看了一些资料后得知,这里面设计到的知识\内容可以写本书了,不是一两句可以说清的,特别是一些商务网站,或者跟钱有关,或者和公司秘密有关的web更是,需要这方面的测试,在外国有一种专门干这一行的人叫安全顾问,可以审核代码,提出安全建议,出现紧急事件是的处理办法等,在国内没有听说哪里有专门搞安全技术测试的内容.

        兼容性 (Compatibility)
       不同浏览器,不同应用程序版本在实现功能时的表现,不同的上网方式,如果你测试的是一个公共网站的话.
        兼容性测试内容详述
        硬件平台
        浏览器软件和版本:浏览器插件,浏览器选项,视频分辨率和色深.文字大小,调制解调器速率.
        软件配置 (Configuration) 如IE浏览器的不用选项-安全设定最高,禁用脚本程序,等等,你们的程序在各种不用的设置下表现如何.
     
        单元测试技术(Unit Test):
        2.2.1 下面是对白盒测试和单元测试的区别的论述:
         单元测试和白盒测试是不同的,虽然单元测试和白盒测试都是关注功能虽然他们都需要代码支持,但是级别不同,白盒测试关注的是类中一个方法的功能是更小的单位,但是完成一个单元测试可能需要N多类,所以说作单元测试需要什么写驱动和稳定桩,比如查询单元是一个查询包包N多的测试类,测试数据,运行他需要提供数据的部分,输入参数和发出命令的驱动等等.是比类大的一个整体进行的.
       另一个明显的区别是白盒测试不会关注类接口,但是单元测试主要的内容就是类接口测试.
       不过很多时候是很少区分的,因为这两种技术实现起来有很多相互关联的部分.不过要看你对质量的关注程度来决定.

    2.2.2 功能测试边界测试\越界测试技术详述

       边界条件
       边界条件是指软件计划的操作界限所在的边缘条件.
       如果软件测试问题包含确定的边界,那么数据类型可能是:
       数值速度字符地址位置尺寸数量。同时,考虑这些类型的下述特征:
       第一个/最后一个最小值/最大值
       开始/完成超过/在内
       空/满最短/最长.
       最慢/最快最早/最迟
       最大/最小最高/最低
       相邻/最远

      越界测试
      通常是简单加1或者很小的数(对于最大值)和减少1或者很小的数(对于最小值),例如:
       第一个减1/最后一个加1
      开始减1/完成加1
      空了再减/满了再加
      慢上加慢/快上加快
      最大数加1/最小数减1
      最小值减1/最大值加1
      刚好超过/刚好在内
      短了再短/长了再长
      早了更早/晚了更晚
      最高加1/最低减1
      另一些该注意的输入:默认,空白,空值,零值和无;非法,错误,不正确和垃圾数据.

    2.2.3 状态测试技术
      软件可能进入的每一种独立状态;
       从一种状态转入另一种状态所需的输入和条件;
       进入或退出某种状态时的设置条件及输入结果.
       具体测试方法可以参考如下:
       每种状态至少访问一次;
       测试看起来最常见最普遍的状态转换;
       测试状态之间最不常用的分支
       测试所有错误状态及其返回值
       测试随机状态转换

       2.2.4 竞争条件测试技术
       竞争条件典型情形参考如下:
       两个不同的程序同时保存或打开同一个文档
       共享同一台打印机,通信端口或者其他外围设备
       当软件处于读取或者修改状态时按键或者单击鼠标
       同时关闭或者启动软件的多个实例
       同时使用不同的程序访问一个共同数据库
     
       2.3 负载\压力测试(StressTest)
        在这里的负载\压力和功能测试中的不同,他是系统测试的内容,是基本功能已经通过后进行的.可以在集成测试阶段,亦可以在系统测试阶段进行.
        使用负载测试工具进行,虚拟一定数量的用户看一看系统的表现,是否满足定义中的指标.
        负载测试一般使用工具完成,loadrunner,webload,was,ewl,e-test等,主要的内容都是编写出测试脚本,脚本中一般包括用户一般常用的功能,然后运行,得出报告。所以负载测试包括的主要内容就不介绍了。
        负载测试技术在各种极限情况下对产品进行测试 (如很多人同时使用该软件,或者反复运行该软件),以检查产品的长期稳定性。例如,使用压力测试工具对web服务器进行压力测试. 本项测试可以帮助找到一些大型的问题,如死机、崩损、内存泄漏等,因为有些存在内存泄漏问题的程序,在运行一两次时可能不会出现问题,但是如果运行了成千上万次,内存泄漏得越来越多,就会导致系统崩滑。用J2EE实现的系统很少但是并不是没有内存问题.
    无论什么工具基本的技术都是利用线程技术模仿和虚拟用户,在这里主要的难点在与测试脚本的编写,每种工具使用的脚本都不一样,但是大多数工具都提供录制功能就算是不会编码的测试人员同样可以测试。
    对负载工具的延伸使用可以进行系统稳定性测试,系统极限测试,如使用100的Load Size连续使用24小时,微软定义的通过准则是通过72小时测试的程序一般不会出现稳定性的问题。
     
    2.4 回归测试 (Regression Test)
         过一段时间以后,再回过头来对以前修复过的Bug重新进行测试,看该Bug 是否会重新出现。
    回归测试技术可以在测试的各个阶段出现,无论是单元测试还是集成测试还是系统测试。是对以前问题进行验证的过程。
    回归测试的目的就是保证以前已经修复的Bug不会再出现。实际上,许多Bug都是在回归测试时发现的,在此阶段,我们首先要检查以前找到的Bug 是否已经更正了。值得注意的是,已经更正的Bug 也可能又回来了,有的Bug 经过修改之后可能又产生了新的Bug。所以,回归测试可保证已更正的Bug不再重现,不产生新的Bug。

    2.5 Alpha 和Beta 测试 (Alpha and Beta Test):
          在正式发布产品之前往往要先发布一些测试版,让用户能够反馈出相关信息,或者找到存在的Bug,以便在正式版中得到解决。 特别是在有客户参加的情况下,对系统进行测试可能会出现一些我们没有考虑的情况,还可以解决一些客户实际关心的问题不同的测试技术区分

    3.1 覆盖测试技术

        说明:测试覆盖率可以看出测试的完成度,在测试分析报告中可以作为量化指标的依据,测试覆盖率越高效果越好。
        覆盖测试可以是程序代码的执行路径覆盖,亦可以是功能实现的步骤覆盖(可以理解成流程图的路径覆盖)。
       该技术可以用在任何测试阶段,包括单元测坏死、集成测试、系统测试。使用该技术时可以使用以上的任何测试方法和测试技术。

    3.2 白盒测试和黑盒测试技术

        白盒测试技术 (White Box Testing)该技术主要的特征是测试对象进入了代码内部,根据开发人员对代码和对程序的熟悉程度,对有需要的部分进行在软件编码阶段,开发人员根据自己对代码的理解和接触所进行的软件测试叫做白盒测试。这一阶段测试以软件开发人员为主,使用Xunit系列工具进行测试,可以包括很多方面如功能性能等。
        黑盒测试 (Black Box Testing)测试的主体部分黑盒测试的内容主要有以下几个方面,但是主要还是功能部分。主要是覆盖全部的功能,可以结合兼容,性能测试等方面进行,包括的不同测试类型请参考以上内容。

    3.3 手工测试和自动化测试

         手工测试(Manual Testing):即依靠人力来查找Bug。方法可以参考上边的测试,也可以根据对实现技术及经验等进行不同的测试。
        自动测试(Automation Testing)使用有针对工具实行。可以作出自动化测试的计划,对可以进行自动化测试的部分编写或者录制相应的脚本,可以加入功能,容错,表单提交等,可以参考MI,Rational或者其他类测试工具说明.
        根据权威的软件测试经验,手工测试还是主要的测试方法,自动测试不够灵活,在这里不再详述。微软的测试过程80%还是手工完成。
        自动测试永远也代替不了手工测试,但是手工测试的工作量很大是不争的事实。

    3.4 根据RUP标准按阶段区分测试

       单元测试在上边有详细的叙述,还有针对单元测试和集成测试的论述,请参考。
    集成测试分为功能集成测试和系统集成测试,相互有调用的功能集成,在系统环境下功能相互调用的影响等,使用方法可以任意选用上面的内容。注重功能方面。
    系统测试在功能实现的基础上,可以加入兼容性,易用性,性能等等
     验收测试可以包括Alpha和Beta测试,在这里就不再详述。
     
    4. 存在风险及解决方法

       说明:测试不能找出所有的问题,只是尽量将问题在开发阶段解决大多数的问题而已。
       测试风险如下:
       软硬件的测试环境提供上也对测试结果有很大的影响。
       测试团队的水平,经验,合作效果等
       整个开发流程对测试的重视程度,测试的进入时间等
       由于测试环境操作系统,网络环境,带宽等情况可能产生的测试结果可能不同这是就需要经验以及对测试环境的保护等方面下一些功夫。

    5. 软件缺陷的原则

       软件缺陷区别于软件bug,它是在测试过程中出现的对系统有影响的,但是在设计中没有的或者对修改后的bug测试和开发人员有不同意见等
    软件未达到产品说明书标明的功能。
    软件出现了产品说明书指明不会出现的错误。
    软件功能超出产品说明书指明范围。
    软件未达到产品说明书虽未指出但应达到的目标。
    软件测试员认为软件难以理解、不易使用、运行速度缓慢,或者最终用户认为不好。
     
    6. 文档测试

      产品说明书属性检查清单
      完整.是否有遗漏和丢失?完全吗?单独使用是否包含全部内容?
      准确.既定解决方案正确吗?目标明确吗?有没有错误?
      精确,不含糊,清晰.描述是否一清二楚?还是自说自话?容易看懂和理解吗?
      一致.产品功能能描述是否自相矛盾,与其他功能有没有冲突?
      贴切.描述功能的陈述是否必要?有没有多余信息?功能是否原来的客户要求?
      合理.在特定的预算和进度下,以现有人力,物力和资源能否实现?
      代码无关.是否坚持定义产品,而不是定义其所信赖的软件设计,架构和代码?
      可测试性.特性能否测试?测试员建立验证操作的测试程序是否提供足够的信息?
      产品说明书用语检查清单
      说明对问题的描述通常表现为粉饰没有仔细考虑的功能----可归结于前文所述的属性.从产品说明书上找出这样的用语,仔细审视它们在文中是怎样使用的.产品说明书可能会为其掩饰和开脱,也可能含糊其词----无论是哪一种情况都可视为软件缺陷.
    总是,每一种,所有,没有,从不.如果看到此类绝对或肯定的,切实认定的叙述,软件测试员就可以着手设计针锋相对的案例.
    当然,因此,明显,显然,必然.这些话意图诱使接受假定情况.不要中了圈套.
    某些,有时,常常,通常,惯常,经常,大多,几乎.这些话太过模糊."有时"发生作用的功能无法测试.
    等等,诸如此类,依此类推.以这样的词结束的功能清单无法测试.功能清单要绝对或者解释明确,以免让人迷惑,不知如何推论.
    良好,迅速,廉价,高效,小,稳定.这些是不确定的说法,不可测试.如果在产品说明书中出现,就必须进一步指明含义.
    已处理,已拒绝,已忽略,已消除.这些廉洁可能会隐藏大量需要说明的功能.
    如果...那么...(没有否则).找出有"如果...那么..."而缺少配套的"否则"结构的陈述.想一想"如果"没有发生会怎样.
  • 基于Windows和linux下开源缺陷跟踪系统Mantis安装和安装配置手册

    2007-01-26 13:35:23

    基于Windows下开源缺陷跟踪系统Mantis安装


    1. 简介
    mantis(螳螂)是一个基于php/MySQL/web的缺陷跟踪系统,最新版本是1.0.1。
    要了解更多信息请到http://www.mantisbt.org/
     
    2. 下载
    mantis-1.0.1                                    http://www.mantisbt.org/download.php
    mysql-5.0.20-win32.zip                     http://dev.mysql.com/downloads/mysql/5.0.html
    php-5.1.2-Win32.zip                         http://www.php.net/downloads.php
    apache_2.0.55-win32-x86-no_ssl.msi   http://httpd.apache.org/download.cgi

    3. 安装步骤
     下面是我安装时设置的目录
       c:\qa
       c:\qa\mantis
       c:\qa\mysql
       c:\qa\php
       c:\qa\apache

      首先安装的是 apache_2.0.55
       Apache是著名的服务器,开始安装的时候总是遇到系统找不到指定的文件,没有安装名为apache2的服务器。在网上查找找到
       如果系统安有IIS服务,再安装Apache,就会出现上面错误。 解决的方法是 把IIS服务停掉,并在“添加删除windows组件”中去除IIS组件。
       根据这样的提示操作之后,我再次安装了Apache,前面几项只需点“NEXT”就行了,到了要求填写network domain、server domain、和email的时候
       如果没有域名,就随便写点,也没有什么影响。
       接下来 配置Apache
       在 c:\qa\apache\conf目录下,打开httpd.conf进行配置apache.查找"DirectoryIndex",这是设定主页的首页的文件名,为了支持PHP,我是这样更改:
       更改前为:DirectoryIndex index.html index.html.var,
       更改后为:DirectoryIndex index.html index.html.var index.php,也就是在其后面添加index.php。
       查找"DocumentRoot",这是指定主页放置的目录,默认为C:/qa/apache/apache2/htdocs,可以使用默认的,也可以自己指定,比如D:/test
      安装 php
      将php-5.1.2-Win32.zip解压缩到c:\qa\php下,把
      fdftk.dll
      fribidi.dll
      gds32.dll
      libeay32.dll
      libmhash.dll
      libmysql.dll
      ntwdblib.dll
      php5isapi.dll
      php5nsapi.dll
      php5ts.dll
      ssleay32.dll
      yaz.dll
      这些文件copy到C:\windows\system32下,同时复制 c:\qa\php 目录下的 php.ini-dist 文件到 C:\windows\ 目录下,并改名为 php.ini。
      配置php.ini
      找到下面两行; Directory in which the loadable extensions (modules) reside. extension_dir = "./"
      将extension_dir 的路径修改为你自己安装的路径,
      我的是; Directory in which the loadable extensions (modules) reside. extension_dir = "C:\qa\php\ext\",
      寻找;default_charset,为了让其支持中文,可以修改默认的字符集,可使用"UTF-8"或者"GB2312"或者"GBK",都行,它默认的是;default_charset="iso-8859-1",修改就行了.
      修改httpd.conf
      添加下面代码。LoadModule php5_module C:/qa/php/php5apache2.dll
      AddType application/x-httpd-php . 到文件的尾部。
      php LoadModule是加载PHP模块,路径一定要准确。
      AddType 是让apache支持PHP类型。 保存httpd.conf。
      测试apache安装结果
      修改好配置文件后,重启apache
      随便编写一个PHP文件放到D:/test下,比如:写上下面几行
      <?
        echo "hello world";
      ?>
      并保存为1.php,打开浏览器,输入:http://localhost/1.php,就会出现hello world,现在Apache+PHP配置已经完全成功了,
      值得注意的一点就是每次修改httpd.conf后,都得重启一次apache才能生效。

      安装Mysql
       Mysql 的安装比较简单,就不一一说明了,因为我想让数据库支持中文 就在安装的时候 把编码该成GBK。
       启动 Mysql的服务。
      部署mantis

       将mantis-1.0.1解压缩到php的发布目录中 c:\qa\mantis,由于1.0.1中取消了原来\sql\db_generate.sql 的数据库脚本而采用了通过系统来建立环境。
      通过浏览器访问http://localhost/mantis/admin/install.php可看到部署mantis数据库环境的界面。
      默认的数据库是bugtracker,注意要使用有管理员权限的用户来建立数据库(建立好bugtracker数据库),我用了root 。
      设定好了install/upgrade database之后正常情况下在输出反馈页面下一片绿色的good,那就是ok了,
      要是有红色的bad,那要调整好了再初始化到ok才行,这时候mysql新增的bugtracker库里建立起很多以mantis_开头的表,到此数据库初始化结束。

      设置Mantis
     将c:\qa\mantis中的config_inc.php.sample复制一份,改名为config_inc.php,修改其中的设置;
     在config_defaults_inc.php中保存这Mantis的默认设置:用户自己的设置信息保存在config_inc.php中,
     如果某个选项在config_inc.php中有设置,则系统使用config_inc.php中的设置,否则使用config_defaults_inc.php的系统默认设置;
     config_inc.php.sample则是Mantis给出的一个用户设置文件例子。
     根据情况修改config_inc.php文件中的设置,设置很简单,各个参数的在config_defaults_inc.php都有很详细的说明。
     下面是我的一些自定义参数,phpmailer的内容之后说明:
    $g_use_iis = ON;            # 使用IIS 
    $g_show_version = OFF;     # 不在页面下部显示 Mantis的版本号
    $g_default_language = 'chinese_simplified';  # 默认语言为简体中文
    $g_show_project_menu_bar = ON;   # 显示项目选择栏
    $g_show_queries_count = OFF;              # 在页脚不显示执行的查询次数 
    $g_default_new_account_access_level = DEVELOPER;                          # 默认用户级别
    $g_window_title = 'Mantis Bug 跟踪管理系统';            # 浏览器标题 
    $g_page_title = 'Rink的BUGs跟踪管理系统';          # 页面标题栏 
    $g_enable_email_notification = ON;                    # 开通邮件通知
    $g_smtp_host = 'smtp.***.com';                   # SMTP 服务器
    $g_smtp_username = '***';                        # 邮箱登录用户名 
    $g_smtp_password = '***';                         # 邮箱登录密码 
    $g_use_phpMailer = ON;                       # 使用 PHPMailer 发送邮件 
    $g_phpMailer_path = 'c:/qa/mantis/core/phpmailer/'; # PHPMailer 的存放路径 
    $g_phpMailer_method = 2;       # PHPMailer 以 SMTP 方式发送 Email 
    $g_short_date_format = 'Y-m-d';      # 短日期格式,Y 大写表示 4 位年 
    $g_normal_date_format = 'Y-m-d H:i';             # 普通日期格式 
    $g_complete_date_format = 'Y-m-d H:i:s';        # 完整日期格式 

    完成以上设置以后,你就可以使用Mantis了,打开浏览器,输入http://localhost/mantis
    应该就可以看到Mantis的登录页面了,你可以用默认用户名administrator和密码root登录进去,进行管理设置。

    Mantis的初步安全设置
    删除admin目录
    在Mantis目录下有一个admin目录,这是Mantis管理员进行管理Mantis的,比如之前我们构建数据库环境的install.php等。
    使用这个模块可以检查你的Mantis是否安装完全,对旧版本的Mantis进行升级,对Mantis的页面CSS文件进行修改;
    使用这个管理模块是不需要用户名和密码的,因此任何人都可以通过这个管理模块查看你的Mantis系统信息,
    而且由于有升级模块,在这里还可以直接对数据库进行修改。
    因此Mantis会建议在配置完成后将这个admin目录删除;注意一定是删除而不是改名!改名后仍然是可以访问的!

    删除administrator
    在添加一个具备管理员权限的用户后,删除系统默认的administrator用户。
     
    PHPMail的设置

    默认情况下,Mantis使用内置的Mailto()函数进行邮件的发送,包括新用户注册发送密码、Bug改变提醒、重设密码等邮件的发送都使用MailTo()来完成,
    不过实际使用中发现,MailTo函数好像不支持需要校验的邮件服务器。
    其自身携带的PHPMailer配置起来也有问题,所以我直接将下载的PHPMailer覆盖Mantis里附带的Mailer
    (c\qa\mantis\core\phpmailer)。来发送邮件。
    关于更多PHPMailer请到http://phpmailer.sourceforge.net
    修改PHP.ini,找到include_path,增加c:\qa\mantis\core\phpmailer目录;
    如上表所示,设置$g_use_phpMailer、$g_phpMailer_path和$g_phpMailer_method三个参数;
    现在应该就可以使用PHPMailer进行邮件发送了;
     
    图形报表(jpgraph)的设置
    默认情况下,Mantis的图形报表是关闭的,因此在Mantis的报表中看不到“图形报表”的入口,
    需要安装JPGraph模块并设置$g_use_jpgraph为ON才能打开图形报表;
    下载JPGraph:从http://www.aditus.nu/jpgraph/index.php下载JPGraph的安装文件,当前最新版本是jpgraph-2.1.1;
    将下载下来的jpgraph-2.1.1.tar.gz解压缩到c:\qa\mantis\core\jpgraph目录下;
    打开config_inc.php文件,修改$g_jpgraph_path为JPGraph的src目录,$g_use_jpgraph为ON;
    也就是 $g_use_jpgraph = ON;                         # 使用图形报表(jpgraph)
          $g_jpgraph_path = c:/qa/mantis/core/jpgraph/src/';    # JPGraph路径  ,注意最后的’/’要加

    修改PHP.ini文件,激活“extension=php_gd2.dll”和“extension=php_iconv.dll”;另外如果extension_dir项不正确,请把extension_dir改为正确的值。
    将PHP\dlls下面的iconv.dll复制到Windows\System32目录下,以上两个步骤使PHP自动载入php_gd2和php_iconv.dll模块,这两个模块是JPGraph在显示图表和进行汉字编码转换是所必须的;
    现在再打开Mantis的统计页面,可以看到多了图形报表,分别按状态等进行统计的图形报表,包括柱图、饼图和线图,但是图形中有很多乱码,那是因为Mantis中默认是通过UTF-8设置JPGraph,而我们界面语言是用简体中文,因此汉字显示出来都是乱码。
    解决方法很简单:
    在Mantis\config_inc.php中将$g_graph_font = ''改为$g_graph_font = 'simsum';
    由于Mantis中图形报表默认字体里不含有中文,因此我们要在Mantis\core\graph_api.php中相应增加对图形标题等设置字体代码;
    在function graph_get_font() {...}中font_map增加'simsum' => FF_SIMSUN ,以供Mantis调用。
    这样以来,在图形报表中就能看到简体中文了。

    补充一下linux下的安装过程
     
    1 所需软件
    1.1 MySQL
    MySQL-server-4.1.10-0.i386.rpm
    MySQL-client-4.1.10-0.i386.rpm
    MySQL-devel-4.1.10-0.i386.rpm
    MySQL-shared-4.1.10-0.i386.rpm
    MySQL-shared-compat-4.1.10-0.i386.rpm
    1.2 Apache
    httpd-2.0.54.tar.gz
    1.3 PHP
    php-5.0.4.tar.gz
    1.4 mantis
    mantis-1.1.1-bin
    1.5 Jpgraph
    jpgraph-1.19.tar.gz
    jpegsrc.v6b.tar.gz
    zlib-1.1.3.tar.gz
    libpng-1.2.8.tar.gz
    freetype-2.1.10.tar.gz
    t1lib-5.1.0.tar.gz
    1.6 Graphviz
    graphviz-2.4-1.rh73.i386.rpm
    graphviz-devel-2.4-1.rh73.i386.rpm
    graphviz-doc-2.4-1.rh73.i386.rpm
    graphviz-graphs-2.4-1.rh73.i386.rpm
    graphviz-tcl-2.4-1.rh73.i386.rpm
     
    1.7 浏览支持
    ZendOptimizer-linuxi386.tar.gz
     
    1.8 CVSWeb
    CVSWeb3.0.0.tar.gz
    2 安装步骤
    2.1 安装Mysql
    安装mysql server
    # rpm -ivh MySQL-server-4.0.20-0.i386.rpm
     
    安装mysql
    # rpm -ivh MySQL-client-4.0.20-0.i386.rpm
     
    安装mysql其他包
    # rpm –ivh MySQL-devel-4.1.10-0.i386.rpm
    # rpm –ivh MySQL-shared-4.1.10-0.i386.rpm
    # rpm –ivh MySQL-shared-compat-4.1.10-0.i386.rpm
     
    运行mysql 客户端,并开放root用户的远程访问权限。以便调试
    # mysql
    > use mysql
    > update user set host = `%` where user = `root` and host <> `localhost`;
    > exit
     
    Mysql安装完毕。
     
    2.2 安装apache
    # tar -zxvf httpd-2.0.54.tar.gz
    # cd httpd-2.0.54
    # ./configure --prefix=/web/apache --enable-module=so
    # make
    # make install
    Apache安装完毕。
     
    说明:apache在linux下的默认最大进程数为256,无论如何修改httpd.conf都不能超过这个限制。如果想加大这个限制,在编译apache前编辑/home/tmp/apache/src/include/httpd.h,将其中#define HARD_SERVER_LIMIT 256 一行改为#define HARD_SERVER_LIMIT 2048后再编译apache。
    2.3 安装GD库
    2.3.1 jpeg-6b 的安装
    # tar -xf jpegsrc.v6b.tar
    # cd jpeg-6b
    # ./configure
    # make
    # make install
     
    2.3.2 zlib 的安装
    # tar –zxvf zlib-1.1.3.tar.gz
    # cd zlib-1.1.3
    # ./configure
    # make
    # make install
     
    2.3.3 libpng 的安装
    # tar –zxvf libpng-1.2.8.tar.gz
    # cd libpng-1.2.8
    # ./configure
    # make
    # make install
     
    2.3.4 freetype 的安装
    # tar –zxvf freetype-2.1.10.tar.gz
    # cd freetype-2.1.10
    # ./configure
    # make
    # make install
     
    2.3.5 T1lib 的安装
    # tar –zxvf t1lib-5.1.0.tar.gz
    # cd t1lib-5.1.0
    # ./configure
    # make
    # make install
     
    2.3.6 gd 的安装
    # tar –zxvf gd-2.0.33.tar.gz
    # cd gd-2.0.33
    # ./configure
    # vi Makefile
    (编辑 Makefile文件
    改 CFLAGS=-O 为 CFLAGS=-O -DHAVE_XPM -DHAVE_JPEG -DHAVE_LIBTTF
    改 LIBS=-lm -lgd -lpng -lz 为 LIBS=-lm -lgd -lpng -lz -ljpeg -lttf -lXpm -lX11
    改 INCLUDEDIRS=-I. -I/usr/local/include -I/usr/include/X11 -I/usr/X11R6/include/X11
    为 INCLUDEDIRS=-I. -I/usr/local/include -I/usr/include/X11 -I/usr/X11R6/include/X11 -I/usr/local/include/freetype)
    # make
    # make install
     
    2.4 安装php
    # tar zxvf php-5.0.4.tar.gz
    # cd php-5.0.4
    #./configure --prefix=/web/php \
     --with-apxs2=/web/apache/bin/apxs \
     --with-config-file-path=/usr/local/lib \
    --enable-track-vars \
    --with-xml \
    --with-mysql \
    --with-libxml-dir=/usr/local/lib \
    --with-gd=/usr/local \
    --with-t1lib=/usr/local \
    --with-tiff-dir=/usr/local \
    --with-jpeg-dir=/usr/include \
    --with-ttf=/usr/include/freetype \
    --with-zlib-dir=/usr/include \
    --with-png-dir=/usr/include \
    --with-mbstring --enable-mbstring=all
     
    # make
    # make install
    # cp php.ini-dist /usr/local/lib/php.ini
     
    修改php.ini中的下列行
    extension_dir = "./"

    extension_dir = "/web/php/include/php/ext"
     
    2.5 GD库的安装检查
    安装完毕后用<? phpinfo(); ?>查看结果如下:
    gd
    GD Support enabled
    GD Version 1.6.2 or higher
    FreeType Support enabled
    FreeType Linkage with TTF library
    T1Lib Support enabled
    GIF Support enabled
    JPG Support enabled
    PNG Support enabled
    WBMP Support enabled
    2.6 安装Mantis
    # mv mantis-1.1.1 /var/www/mantis
    2.7 建立数据库bugtracker及用户mantis
    $mysqladmin -u root -p create bugtracker
    //输入MySQL的root密码即可完成创建数据库bugtracker的操作
    $mysql -u root -p
    mysql>grant all privileges on bugtracker.* to 'mantis'@'localhost' identified by '你指定的mantis用户密码';
    mysql>FLUSH PRIVILEGES;
    mysql>\q
    2.8 安装Zend Optimizer
    #tar -zxvf ZendOptimizer-linuxi386.tar.gz
    #cd ZendOptimizer-2.6.0-linux-glibc21-i386
    #./install.sh
    //重启Apache
    #/web/apache/bin/apachectl –k restart
    2.9 配置mantis
    # cd /var/www/mantis
    # vi config_inc.php
     
    //修改以下几行
    $g_db_username = "mantis";
    $g_db_password = "你在建立用户时指定的密码";
    $g_database_name = "bugtracker";
     
    //增加以下几行
    $g_path = "http://www.yourdomain.com/mantis/";
    $g_icon_path = $g_path."images/";
    $g_absolute_path = "/var/www/mantis/";
    $g_use_iis = OFF;
    $g_show_version = ON;
     
    //以下是配置邮件的,Mantis使用邮件来进行注册和通知,所以必须配置好
    $g_enable_email_notification = ON; # 开通邮件通知
    $g_smtp_host = 'mail.softbrain.com.cn';   # SMTP 服务器
    $g_smtp_username = 'esm@softbrain.com.cn'; # 邮箱登录用户名                      
    $g_smtp_password = '对应用户邮箱的密码'; # 邮箱登录密码                         
    $g_use_phpMailer = ON;   # 使用 PHPMailer 发送邮件                               
    $g_phpMailer_path = '/usr/local/php/includes/PHPMailer/'; # PHPMailer 的存放路径     
    $g_phpMailer_method = 2;   # PHPMailer 以 SMTP
     
    $g_show_version = OFF;                  # 不在页面下部显示 Mantis的版本号
    $g_default_language = ’english’;              # 默认语言为英语
    $g_default_new_account_access_level = DEVELOPER; # 默认用户级别
    $g_use_jpgraph= ON;                             # 使用图形报表
    $g_jpgraph_path = ’/web/php/include/jpgraph/src/’;    # JPGraph路径  
    $g_window_title = ’Software Quality Manager’;     # 浏览器标题
    $g_page_title   = ’ Software Quality Manager’;     # 页面标题栏
     
    2.10 创建数据库的表结构
    #cd /var/www/mantis
    #mysql -u mantis -p bugtracker<sql/db_generate.sql
    //输入数据库密码即可创建
    2.11 配置httpd.conf
    编辑apache的httpd.conf
    #vi /web/apache/conf/httpd.conf
    //增加以下几行
    LoadModule php5_module        modules/libphp5.so
    AddType application/x-httpd-php .php .phtml .php3 .inc
     
    Alias /mantis/ "/var/www/mantis/"
    <Directory “/var/www/mantis/”>
    Options Indexes MultiViews Includes FollowSymLinks +ExecCGI
    AllowOverride None
    Order allow, deny
    Allow from all
    </Directory>
     
    找到DirectoryIndex index.html index.html.var,将其改为
    DirectoryIndex index.html index.html.var index.php
     
    2.12 重启apache服务
    # /web/apache/bin/apachectl –k restart
     
    2.13 访问Mantis
    通过浏览器访问:http://www.yourdomain.com/mantis/
    如果出现登录页面,则一切Ok!
    升级数据库
    登录http://www.yourdomain.com/mantis/admin
    点击“Upgrade your installation”,升级其中的“Basic upgrade set (required)”和“String escaping fixes (recommended)”。
    然后,
    #mysql –u mantis –p bugtracker<sql/db_update.sql
    并初始化数据库
    #mysql –u mantis –p bugtracker<sql/db_insert_data.sql
     
    2.14 Mantis的初步安全设置
    在Mantis目录下有一个admin目录,如果你在IE中打开这个目录下的index.php查看,你就会知道这个目录是进行Mantis Administration的,使用这个模块可以检查你的Mantis是否安装完全,对旧版本的Mantis进行升级,对Mantis的页面CSS文件进行修改;使用这个管理模块是不需要用户名和密码的,因此任何人都可以通过这个管理模块查看你的Mantis系统信息,而且由于有升级模块,在这里还可以直接对数据库进行修改;因此,在配置完成后将这个admin目录删除。
    在添加一个管理员用户后,删除系统默认的administrator用户。
    3 CVS集成配置
    如果需要建立Mantis 和 CVS集成,需要将Mantis与CVS服务安装在同一台服务器上。
    3.1 安装CVSWeb
    CVSWEB就是CVS的WEB界面,可以大大提高程序员定位修改的效率。
    解包
    tar -zxvf  CVSWeb3.0.0.tar.gz
     
    把配置文件cvsweb.conf复制到apache的配置目录下
    cp cvsweb.conf /path/to/apache/conf
    转到/path/to/apache/conf下并修改cvsweb.conf:
    修改CVSROOT路径设置:
    %CVSROOT = (
    'Development' => '/path/to/cvsroot/dev', #<==修改指向本地的CVSROOT
    'test' => '/path/to/cvsroot/test', #<==修改指向本地的CVSROOT
    );
    如果有多个cvsroot,这定缺省的cvsroot
    $cvstreedefault = 'test';
     
    其它个性化设置
    cvsweb.conf还有许多其它个性化设置,常见的有这些变量:
    $logo 图标设置
    $defaulttitle 标题设施
    $address 管理员email地址设置
    $long_intro 介绍文字
    $short_instruction 说明文字
     
    把文件cvsweb.cgi复制到apache的cgi目录
    cp cvsweb.cgi /path/to/apache/cgi-bin
    转到/path/to/apache/cgi-bin修改cvsweb.cgi
    修改cvsweb.cgi让CGI找到配置文件:
    $config = $ENV{'CVSWEB_CONFIG'} || '/path/to/apache/conf/cvsweb.conf';
     
    中文支持
    让cvsweb正确显示中文,找到sub html_header($)函数,
    然后在<html>和<title>之间插入一行,修改如下
    <html>
    <meta. http-equiv="Content-Type" content="text/html; charset=gb2312">
    <title>$title</title>
    复制所有的gif,png文件到apache的icons目录
     
    增加访问控制
    CVSWEB可不能随便开放给所有用户,因此需要使用WEB用户认证:
    先生成 passwd:
    /path/to/apache/bin/htpasswd -c cvsweb.passwd user
     
    修改httpd.conf,增加
    <Directory "/path/to/apache/cgi-bin/cvsweb/">
    AuthName "CVS Authorization"
    AuthType Basic
    AuthUserFile /path/to/cvsweb.passwd
    require valid-user
    </Directory>
    3.2 配置config_inc.php
    设置mantis配置文件config_inc.php
    //添加下面一行
    $g_cvs_web = 'http://192.168.100.17/cgi-bin/cvsweb.cgi/';     #CVSWeb链接地址
    $g_source_control_account=’cvsuser’; #必须是mantis的有效帐户;
    3.3 配置CVS配置文件loginfo
    在cvs仓库的配置文件loginfo中添加一行:
    DEFAULT /usr/local/bin/php /var/www/mantis/core/checkin.php %{,sVv} $USER
    这样,在提交cvs文件时,在log message中输入包含有“issue #nnnn”的内容,就会将提交的版本信息,添加在该issue的note中。
    4 Relationship Graph功能配置
    4.1 修改config_inc.php配置
    添加下面内容:
           # --- Relationship Graphs -----------
           # Show issue relationships using graphs.
           #
           # In order to use this feature, you must first install either GraphViz
           # (all OSs except Windows) or WinGraphviz (only Windows).
           #
           # Graphviz homepage:    http://www.research.att.com/sw/tools/graphviz/
           # WinGraphviz homepage: http://home.so-net.net.tw/oodtsen/wingraphviz/
           #
           # Refer to the notes near the top of core/graphviz_api.php and
           # core/relationship_graph_api.php for more information.
     
           # Enable relationship graphs support.
           $g_relationship_graph_enable              = ON;
     
           # Font name and size, as required by Graphviz. If Graphviz fails to run
           # for you, you are probably using a font name that gd can't find. On
           # Linux, try the name of the font file without the extension.
           $g_relationship_graph_fontname          = 'Arial';
           $g_relationship_graph_fontsize            = 10;
     
           # Local path where the above font is found on your system for Relationship Graphs
           # You shouldn't care about this on Windows since there is only one system
           # folder where fonts are installed and Graphviz already knows where it
           # is. On Linux and other unices, the default font search path is defined
           # during Graphviz compilation. If you are using a pre-compiled Graphviz
           # package provided by your distribution, probably the font search path was
           # already configured by the packager.
           #
           # If for any reason, the font file you want to use is not in any directory
           # listed on the default font search path list, you can either: (1) export
           # the DOTFONTPATH environment variable in your webserver startup script
           # or (2) use this config option conveniently available here. If you need
           # to list more than one directory, use colons to separate them.
     
           # Since 0.19.3 we use the $g_system_font_folder variable to define the font folder
     
           # Default dependency orientation. If you have issues with lots of childs
           # or parents, leave as 'horizontal', otherwise, if you have lots of
           # "chained" issue dependencies, change to 'vertical'.
           $g_relationship_graph_orientation = 'horizontal';
     
           # Max depth for relation graphs. This only affects relation graphs,
           # dependency graphs are drawn to the full depth. A value of 3 is already
           # enough to show issues really unrelated to the one you are currently
           # viewing.
           $g_relationship_graph_max_depth              = 10;
     
           # If set to ON, clicking on an issue on the relationship graph will open
           # the bug view page for that issue, otherwise, will navigate to the
           # relationship graph for that issue.
           $g_relationship_graph_view_on_click = OFF;
     
           # Complete path to dot and neato tools. Your webserver must have execute
           # permission to these programs in order to generate relationship graphs.
           # NOTE: These are meaningless under Windows! Just ignore them!
           $g_dot_tool                                              = '/usr/bin/dot';
           $g_neato_tool                                    = '/usr/bin/neato';
     
    4.2 安装Graphviz
    # rpm –ivh graphviz-2.4-1.rh73.i386.rpm
    # rpm –ivh graphviz-devel-2.4-1.rh73.i386.rpm
    # rpm –ivh graphviz-doc-2.4-1.rh73.i386.rpm
    # rpm –ivh graphviz-graphs-2.4-1.rh73.i386.rpm
    # rpm –ivh graphviz-tcl-2.4-1.rh73.i386.rpm


  • 软件版本标志

    2007-01-26 11:45:30

    软件版本标志

        Trial:试用版,软件在功能或时间上有所限制,如果想解除限制,需要购买零售版。

      Retail:零售版。

      Free:免费版。

      Full:完全版。

      Alpha:内部测试版,通常在Beta版发布之前推出。

      Beta:测试版,正式版推出之前发布的版本。以上两种测试版本Bug可能较多。

      Final:正式版,软件的正式版本,修正了Alpha版和Beta版的Bug。

      SR:修正版或更新版,修正了正式版推出后发现的Bug。

      Pro:专业版,需要注册后才能解除限制,否则为评估版本。

      Plus:加强版。

      Delux:豪华版。Plus版和Delux版区别不大,比普通版本多了一些附加功能。

      Build:内部标号,同一版本可以有多个Build号,通常Build后面的数字越大,软件版本越新。某些软件后面的数字为发布时间,例如:Windows 优化大师 v5.4 Build 602。
     
     
     
     
     
       WinXP VOL,VLK,FPP,RTM的含义经常有人找中文,英文免激活xp,找到了一大堆版本又不知道用那个好,这里放出微软这些恐怖编号的真正含义.

         retail[full packaged product (fpp)]:零售版

    就是在各大软件店看到有漂亮包装的那种。可以升级安装,也可以全新安装。 但是需要激活,机器配置更换了还要通知微软,是比较痛苦的东西。

           oem(original equipment manufacturer):随机版
    只能随机器出货,不能零售,所以叫做随机版。只能全新安装,不能从旧有操作系统升级。如果买笔记型计算机或品牌计算机就会有随机版软件。包装不像零售版精美,通常只有一片cd和说明书(授权书)。这种系统通常会少一些驱动,而且目前的oem版很少放在光盘里能给你安装,要么就是恢复盘,要么就是硬盘镜像。

           upgrade:升级版

    不细说了,地球人都知道。包装和零售版一样,不过价格却便宜很多。只能升级安装,要全新安装的话,安装程序会检查旧有的操作系统光盘,检查过后就可以装了。大家估计是不会需要这种版本的。

           beta:测试版

    这个阶段的版本会一直加入新的功能。

          rc(release.candidate):发行候选版本

    和beta版最大的差别在于beta阶段会一直加入新的功能,但是到了rc版本,几乎就不会加入新的功能了,而是着重于除错!rc1就代表发行侯选第一版。真要253微软,想出什么rc,其实不就是最新更新的beta嘛。

          rtm(release.to.manufacturing)

    正式在零售商店上架前,需要一段时间来压片,包装、配销,所以程序代码必须在正式发行前一段时间就要完成,这个完成的程序代码叫做final.code,这次Windows.xp开发完成,外国媒体用Windows xp.goes.gold来称呼。程序代码开发完成之后,要将母片送到工厂大量压片,这个版本就叫做rtm版。所以说,rtm版的程序码一定和正式版一样。但是和正式版也有不一样的地方:例如正式版中的oem不能升级安装,升级版要全新安装的话会检查旧版操作系统光盘等,这些就是rtm和正式版不同的地方,但是它们的主要程序代码都是一样的。 严格的说这种版本还是属于fpp零售版,需要激活的那种。

          最后vol出场。

    (volume licensing for organizations):团体批量许可证(大量采购授权合约)。可能有人会认为这个不是vlo么?其实vol是取了volume的前3个字母而已,不是3个词的字母缩写。比如英文wxp pro的vol版本的光盘卷标就是wxpvol_en,其中wx表示是Windows xp,p是professional(vol没有home版本),vol表明是团体批量许可证版本,en是表明是英语。获得途径主要是集团购买,某些 msdn用户也可以得到。这种版本根据购买数量等又细分为“开放式许可证”、“选择式许可证”、“企业协议”、“学术教育许可证”等以下5种版本:
    open license
    select license
    Enterprise agreement
    Enterprise subion agreement
    academic volume licensing

    关于vlk:

    volume licensing (product) keys,即vlk,它所指的只是一个key(密匙),仅仅是一个为证明产品合法化、以及安装所使用的key,因为根据vol计划规定,vol产品是不需要激活的!或者说,vlk不是指一种版本,而是指这种版本在部署(deploy)过程中所需要的key,而需要vlk这种key的版本应该叫做vol!只不过在实际中,没有必要强调这种叫法、称呼的准确性,加之很多人的vol版本光盘是通过企业的选择式许可证、企业协议等方式得到的等等原因,所以才会有很多人叫他为“选择版”等等。

    官方网站有一个表格,上面有一句话:“different products require different volume licensing keys (vlks). refer to the Table below to make sure you have the correct vlk for your microsoft product.”,我想这就很好的说明了vlk指的是key而不是产品了。

    很明显的,fpp需要激活,vol不需要激活。大家都来使用vol吧!

    最后说说winxp常见的professional和home的区别。

    winxp共有3个系列,professional,home edition,64-bit edition。最后一种不是一般微机能装的,用于工作站电脑,就不提了。

    最基本的概念就是professional的功能比home edition强!

    home edition既不能归为域,又没有连接netware服务器的功能,也不能使用远程桌面和文件加密功能等。professional可以同时支持双 cpu和最多达9个显示器,而home edition只能支持单cpu和单显示器,home edition中的所有功能在professional中都具备,而且professional在组策略、安全管理、多国语言支持、网络管理和配置等方面更为出色。另外,home edition的升级安装只能从Windows 98/98se/me上进行,而无法在Windows 95/nt4/2000中完成;而profession就没有这个限制。
  • 基于windows的TestLink1.6.2安装

    2007-01-26 11:40:35

    基于windows的TestLink1.6.2安装

    TestLink是基于Web的,开源的测试管理和执行系统。
    它可以让测试团队在创建和管理测试用例的同时,在测试计划中对测试用例进行组织。
    TestLink可以和Bugzilla、Mantis、Jira等缺陷管理工具进行集成。
    因为前面我使用了Mantis缺陷管理工具,所以我这里是与Mantis进行集成。
    本文的描述适用于在windows上的安装。
    1.    介绍
           TestLink目前最新的版本是1.6.2,它的运行需要一些应用程序的支持。
           我的环境配置为:MySQL5.0.19+php5.1.2+Webserver(Apache2.0.55)+Mantis1.0.1。
         
           下面我对这些应用程序的安装与配置步骤分别进行描述。
    2.     安装Apache2.0.55
    3.     安装php
               把 php.ini 中的session.save_path值修改为C:\windows\temp
    4.     安装MySQL
    5.     安装Mantis1.0.1
                 请参阅http://spaces.msn.com/yudiexin/blog/cns!E79542146281C46C!169.entry
    6.      安装TestLink1.6.0
                TestLink1.6.0的安装包可在TestLink网站http://testlink.sourceforge.net/docs/testLink.php 下载。
                1.6.2版本的下载地址为:http://osdn.dl.sourceforge.net/sourceforge/testlink/testlink-1.6.2.zip
               在安装TestLink1.6.2前,需要完成以下步骤:
    6.1.      安装运行所需要的环境:Webserver、php5和MySQL。可以参照前面的介绍进行安装。
    6.2.      将TestLink安装包解压缩到Apache2的htdocs目录下,并重命名为testlink
    6.3.      TestLink的安装有两种方式,一是自动安装,二是手工安装的方式。
    A        自动安装
    A.1      在浏览器输入访问地址http://yoursite/testlink/install/index.php
                如:http://localhost/testlink/install/index.php

    A.2     选择new install,在进入的页面中,输入登录MySQL的用户名和密码,
                如root,如果没有为TestLink新建一个专用的用户,也可以输入初始用户root。
                但是因为root权限过多,所以,建议为TestLink新建专用的用户,
                并为该用户赋予ALTER、SELECT、INSERT、UPDATE、INDEX、CREATE、DELETE和DROP权限。
    A.3    提示安装成功。系统为testlink创建一个默认管理员账号,用户名和密码为:admin/admin。
                你可以使用这个账号访问TestLink
    小结:    在自动安装过程中,安装程序主要做了以下事情:检查web server的参数、php配置、数据库版本、
               创建数据库和表,并导入初始数据、在完成安装后,系统会为testlink创建一个数据库配置文件config_db.inc.php。
    B       手工安装       
               你也可以通过手工的方式来完成TestLink的安装。安装步骤如下:         
    B.1.   我安装了MySQL Control Center ,点击进入控制台直接创建新的数据库 testlink;
    B.2.   在新创建的数据库中,打开 query 窗口,将testlink-1.6.2\install\sql 下的testlink_create_tables.sql、testlink_create_default_data.sql
               文件的内容先后拷贝到query 窗口执行,这样testlink数据库中的表就建好了。
    B.3.   为TestLink新建一个专用的用户,在 MySQL Control Center 的add user 窗口中 新建用户testlink,密码testlink,把它与testlink数据库关联起来。
    B.4.       创建TestLink数据库配置文件。参照下面的代码创建<testlinkdir>/config_db.inc.php文件
    <?php // Automatically Generated by TestLink Installer
       define('DB_TYPE', 'mysql');
       define('DB_USER', 'testlink');
       define('DB_PASS', 'testlink');
       define('DB_HOST', 'localhost');
       define('DB_NAME', 'testlink');
    ?>
    B.5.    打开浏览器,输入:http://localhost/testlink/index.php,可以打开TestLink登陆界面。
    7.       无论是自动安装还是手工安装,安装成功后,要将安装目录下的install目录移走,并在系统中修改admin的初始密码。
    8.      修改配置文件
                <testlink installation directory>/config.inc.php - 主要的配置文件,后面会做详细介绍
                a.数据库的编码
                      define('DB_SUPPORTS_UTF8', TRUE) 修改为  define('DB_SUPPORTS_UTF8', FALSE)
                b.TestLink 中文显示 
                      define('TL_TPL_CHARSET', DB_SUPPORTS_UTF8  ? 'UTF-8' : 'ISO-8859-1');
                      //define('TL_TPL_CHARSET', 'gb2312'); // Chinese charset
                修改为:
                      //define('TL_TPL_CHARSET', DB_SUPPORTS_UTF8  ? 'UTF-8' : 'ISO-8859-1');
                     define('TL_TPL_CHARSET', 'gb2312'); // Chinese charset
                c.与缺陷管理工具的集成配置
             
                      define('TL_INTERFACE_BUGS', 'NO') 修改为  define('TL_INTERFACE_BUGS', 'MANTIS')       
                修改 <testlink installation directory>/cfg/ 下的 mantis.cfg.php 文件
                  define('BUG_TRACK_DB_HOST', 'localhost');
                  define('BUG_TRACK_DB_NAME', 'bugtracker');  mantis 安装时配置的数据库名
                  define('BUG_TRACK_DB_USER', 'root');
                  define('BUG_TRACK_DB_PASS', 'root');
             
                  define('BUG_TRACK_HREF', "http://localhost/mantis/");
                  define('BUG_TRACK_ENTER_BUG_HREF',"http://localhost/mantis/");   所指向的mantis的链接
             
             
    这样就OK了。
     
  • delphi 开发和测试2004-2006年总结经验【五】

    2007-01-25 15:49:08

    Delphi面向对象编程的20条规则(By Marco Cantu) lotusswan(自译)


    关键字      面向对象编程,OOP,Delphi,规则
      
    出处      《The Delphi Magazine》 Issue 47
    作者简介
                Marco Cantu是一个知名的Delphi专家,他曾出版过《精通Delphi》系列丛书,
    《Delphi开发手册》以及电子书《精通Pascal》(该电子书可在网上免费获得)。他讲授的课
    题是Delphi基础和高级开发技巧。你可以通过他的网站(www.marcocantu.com)获得更多关
    于他的信息,你也可以他的公共新闻组和他联系,详情请参见他的网站。


    前言
            大多数Delphi程序员都像使用Visual Basic 那样使用他们手头上开发工具,而丝毫
    没有意识到Delphi的强大功能,更谈不上使用这些功能了。(写到这里,编辑惶恐的举起了手
    ,怎么可能呢?)Delphi和Visual Basic不同,Delphi完全建立在面向对象结构上,这不仅影
    响到VCL的结构,而且影响到使用Delphi开发的每一个程序。
            在本文中,我不想涉及到面向对象编程(OOP)的所有理论,只是提出一些简单的经验
    规则。希望这些规则能够帮助改善你的程序结构。无论你开发的是何种类型的程序,这些经
    验规则都是适用的。你应当把他们当作一些建议,记住他们并把他们应用到你开发的程序中
    去。
           关于面向对象编程,我想强调的一个关键原理是封装。我们都希望创建一些灵活而且
    强健的类,因为这样的类允许我们以后修改他们的实现方法而不影响到程序中的其他部分,
    这正是封装给我们带来的好处。虽然封装不是创建一个好的面向对象程序的唯一标准,但是
    它构成了面相对象编程的基础,所以在本文中我也许会过多的强调封装性,请不要感到奇怪
    ,我有足够充分的理由这么做。
           最后,我想说明这样一个事实:本文将主要集中说明窗体(Forms)的开发(虽然其中
    的一些规则对于组件的开发同样适用),因此这些规则对于所有的Delphi程序员都是适用的
    。那些编写组件的程序员必须把面相对象编程和类(Class)作为核心的元素,但是对于那些
    使用组件编程的程序员,他们时常会忘记面向对象。对于他们,本文可以当作一个提示,提
    醒他们始终记住面向对象编程

    第一部分:窗体是类(A Form. is A Class)(rule 1-rule 15)
           程序员常常将窗体看作是对象,而事实上窗体是类。两者的差别在于你创建基于相同
    的窗体类的多个窗体对象。令人感到疑惑的是Delphi为你定义的每一个窗体类创建了一个默
    认的全局对象。这对于新手来说是相当方便的,但是这同样会使他们形成坏习惯。


    第二部分:继承(Inheritance)(rule 15-rule 20)
           在讲述了一系列关于类特别是关于窗体类的规则后,第二部分将是一些关于类的继承
    性以及可视化窗体继承的建议和技巧。


    关于代码
           本文中所有的代码段都可以在本期杂志(《The Delphi Magazine》 Issue 47)附带
    的磁盘中的OopDemo工程中找到。你特别应该查看例程中的frm2 单元(unit)和inher单元。
    如果你想使用这些代码,请注意构造器必要的初始化设置以及私有组件参照,同时有必要设
    置好窗体的OldCreateOrder属性。否则,带有组件的窗体构造器的初始化代码将在窗体的On
    Create事件之前得到执行。
            在这张磁盘上你还可以找到OOP 窗体向导的第一版的编译包,不过我更希望你访问我
    的网站获得该程序的更完整的版本。

    规则一:为每一个类创建一个单元(One Class,One Unit)
            请始终牢记这一点:类的私有(private)和保护(protected)的部分只对于其他单元中
    的类和过程(procedure)才是隐藏的.因此,如果你想得到有效的封装性,你应该为每一个
    类使用一个不同的单元。对于一些简单的类,比如那些继承其他类的类,你可以使用一个共
    享的单元。不过共享同一个单元的类的数目是受到限制的:不要在一个简单的单元里放置超
    过20个复杂的类,虽然Borland公司的VCL代码曾经这样做过。
           如果你使用窗体的时候,Delphi会默认的遵循“一个类使用一个单元”的规则,这对
    于程序员来说也是十分方便的。当你向你的项目中添加一个没有窗体的类时,Delphi也会创
    建一个新的独立的单元。


    规则二:为组件命名(Name Components)
            为每一个窗体和单元给出一个有意义的名字是十分重要的。窗体和单元的名字必须是
    不同的,不过我趋向于为他们两者使用相似的名字,如对于关于窗体和单元可以为他们使用
    AboutForm. 和About.pas.
            为组件使用带有描述性的名字同样十分重要。最常见的命名方式是使用类的小写字母
    开头,再加上组件的功能,如BtnAdd 或者editName。采用这样的命名方式为组件命名可能会
    有很多相似的名字,而且也没有一个最好的名字,到底应该选择那一个应该依据你的个人爱
    好而定。


    规则三:为事件命名(Name Events)
            对于事件处理方法给出合适的名字更加重要。如果你对于组件给出了一个合适的名字
    ,那么系统默认的名字ButtonClick将变成BtnAddClick。虽然从这个名字中我们可以猜到这
    个事件处理程序的功能,但是我认为使用一个能够描述该方法的作用的名字,而不是采用De
    lphi附加的名字是一种更好的方式。例如,BtnAdd按钮的OnClick事件可以命名成AddToList
    。这会使得你的程序可读性更强,特别是当你在这个类的其他方法中调用这个事件处理程序
    时,而且这会帮助程序员为类似的事件或是不同的组件选用相同的方法。不过我必须声明,
    使用动作(Actions)是目前开发重要的程序时我最喜欢的方法。


    规则四:使用窗体方法(Use Form. Methods)
            窗体都是一些类,因此窗体的代码是以方法组织的。你可以向窗体中添加事件处理程
    序,这些处理程序完成一些特别的功能,而且他们能被其他方法调用。除了事件处理方法外
    ,你还可以向窗体添加完成动作的特别定义的方法以及访问窗体状态的方法。在窗体中添加
    一些公共的(Public)方法供其他窗体调用要比其他窗体直接操作他的组件要好。


    规则5:添加窗体构造器(Add Form. Constructors)
           在运行时创建的第二个窗体除了一个默认的构造器(从Tcomponent 类继承而来)外还
    会提供其他特殊的构造器。如果你不需要考虑和Delphi4以前的版本的兼容性问题,我建议你
    重载(Overload)Create方法,添加必要的初始化参数。具体代码可参见下面的代码:

    Public
        Constructor Create(Text:string): reintroduce ; overload;
    Constructor TformDialog.Create(Text:string);
    Begin
        Inherited Create(Application);
        Edit1.Text:=Text;
    End;



    规则6:避免全局变量(Avoid Global Variables)
             应该避免使用全局变量(就是那些在单元的interface 部分定义的变量)。下面将
    会有一些建议帮助你如何去做。
             如果你需要为窗体存储额外的数据,你可以向窗体类中添加一些私有数据。这种情
    况下,每一个窗体实例都会有自己的数据副本。你可以使用单元变量(在单元的implementa
    tion部分定义的变量)声明那些供窗体类的多个实例共享的数据。
    如果你需要在不同类型的窗体之间共享数据,你可以把他们定义在主窗体里来实现共享,或
    者使用一个全局变量,使用方法或者是属性来获得数据。


    规则7:永远不要在Tform1类中使用Form1(Never Use Form1 in Tform1)
            你应该避免在类的方法中使用一个特定的对象名称,换句话说,你不应该在TForm1类
    的方法中直接使用Form1.如果你确实需要使用当前的对象,你可以使用Self关键字。请牢记
    :大多数时候你都没有必要直接使用当前对象的方法和数据。
    如果你不遵循这条规则,当你为一个窗体类创建多个实例的时候,你会陷入麻烦当中。


    规则8:尽量避免在其他的窗体中使用Form1(Seldom Use Form1 In Other Forms )
           即使在其他窗体的代码中,你也应该尽量避免直接使用全局变量,如Form1.定义一些
    局部变量或者私有域供其他窗体使用会比直接调用全局变量要好。
            例如,程序的主窗体能够为对话框定义一个私有域。很显然,如果你计划为一个派生
    窗体创建多个实例,这条规则将是十分有用。你可以在主窗体的代码范围内保持一份清单,
    也可以更简单地使用全局Sreen对象的窗体数组。

    规则9:移除Form1(Remove Form1)
           事实上,我的建议是在你的程序中移除Delphi自动创建的全局窗体对象。即使你禁止
    了窗体的自动添加功能,这也有可能是必要的,因为在Delphi随后仍然可能添加这样的窗体
    。我给你的建议是应该尽量避免使用全局窗体对象。
            我认为对于Delphi新手而言,移除全局窗体对象是十分有用的,这样他们不至于对类
    和全局对象两者的关系感到疑惑。事实上,在全局窗体对象被移除后,所有与它有关的代码
    都会产生错误。


    规则10:添加窗体属性(Add Form. Properties)
            正如我已经提到过的,当你需要为你的窗体添加数据时,请添加一个私有域。如果你
    需要访问其他类的数据,可以为你的窗体添加属性。使用这种方法你就能够改变当前窗体的
    代码和数据(包含在它的用户界面中)而不必改变其他窗体或类的代码。
            你还应该使用属性或是方法来初始化派生窗体或是对话框,或是访问他们的最终状态
    。正如我前文所说的,你应该使用构造器来完成初始化工作

    规则11:显示组件属性(Expose Components Properties)
            当你需要访问其他窗体的状态时,你不应该直接访问它的组件。因为这样会将其他窗
    体或其它类的代码和用户界面结合在一起,而用户界面往往是一个应用程序中最容易发生改
    变的部分。最好的方法是,为你需要访问的组件属性定义一个窗体属性。要实现这一点,可
    以通过读取组件状态的Get方法和设置组件状态的Set方法实现。
    假如你现在需要改变用户界面,用另外一个组件替换现有的组件,那么你只需做的是修改与
    这个组件属性相关的Get方法和Set方法,而不必查找,修改所有引用这个组件的窗体和类的
    源码。详细实现方法请参见下面的代码:

    private
        function GetText:String;
        procedure SetText(const Value:String);
    public
        property Text:String;
        read GetText write SetText;
    function TformDialog.GetText:String;
    begin
          Result:=Edit1.Text;
    end;
    procedure TformDialog.SetText(const Value:String);
    begin
          Edit1.Text;=Value;
    end;


    规则12:属性数组(Array Properties)
            如果你需要处理窗体中的一系列变量,你可以定义一个属性数组。如果这些变量是一
    些对于窗体很重要的信息,你还可以把他们定义成窗体默认的属性数组,这样你就可以直接
    使用SpecialForm[3]来访问他们的值了。
            下面的代码显示了如何将一个listbox组件的项目定义成窗体默认的属性数组。

    type
        TformDialog =class(TForm)
        private
               listItems:TlistBox;
               function GetItems(Index:Integer):String;
                procedure SetItems(Index:Integer:const Value:String);
       public
                property Items[Index:Integer]:string;
    end;
       function TFormDialog.GetItems(Index:Integer):string;
    begin
            if Index >=ListItems.Items.Count then
                 raise Exception.Create(‘TformDialog:Out of Range’);
                 Result:=ListItems.Items[Index];
    end;
    procedure TformDialog.SetItems(Index:Integer;const alue:string);
    begin
            if Index >=ListItems.Items.Count then
                 raise Exception.Create(‘TformDialog:Out of Range’);
                 ListItems.Items[Index]:=Value;
    end;


    规则13:使用属性的附加作用(Use Side-Effects In Properties)
            请记住:使用属性而不是访问全局变量(参见规则10、11、12)的好处之一就是当你
    设置或者读取属性的值时,你还可能有意想不到的收获。
            例如,你可以直接在窗体界面上拖拉组件,设置多个属性的值,调用特殊方法,立即
    改变多个组件的状态,或者撤销一个事件(如果需要的话)等等。


    规则14:隐藏组件(Hide Components)
            我经常听见那些面向对象编程的狂热追求者抱怨Delphi窗体中包含一些在published
    部分声明的组件,这是和面向对象思想的封装性原理不相符合的。他们确实提出了一个重要
    的议题,但是他们中的大多数人都没有意识到解决方法其实就在他们手边,完全不用重写De
    lphi代码,也不用转向其他语言。
             Delphi向窗体中添加的组件参照可以被移到private部分,使得其他窗体不能访问他
    们。如果你这样做,你就有必要设置一些指向组件的窗体属性(请参见规则11),并且使用
    它们来访问组件的状态。
             Delphi将所有的这些组件都放在published部分,这是因为使用这种方式能够保证这
    些域一定是在.DFM文件中创建的组件。当你改变一个组件的名称时,VCL能够自动地将这个组
    件对象与它在窗体中的参照关联起来。因为delphi使用RTTI和Tobject方法来实现这种功能,
    所以如果想要使用这种自动实现功能,就必须把参照放置在published部分(这也正是为什么
    delphi将所有的组件都放在published部分的缘故)。
            如果你想知道的更详细一点,可以参看下面的代码:

    procedure Tcomponent.SetReference(Enable:Boolean);
    var
          Field:^Tcomponent;
    begin
          If Fowner<> nil then begin
                Field:=Fowner.FieldAddress(Fname);
                If Field<>nil then
                Field^:=Self
         else
                Field^:=nil;
          end;
    end;


    上面的代码是Tcomponent类的SetReference方法,这个方法可以被InserComponent,Remove
    Component和SetName等方法调用。
            当你理解了这一点后,你应该不难想到如果你将组件参照从published部分移到了pr
    ivate段,你将失去VCL的自动关联功能。为了解决这个问题,你可以通过在窗体的OnCreate
    事件中添加如下代码解决:
    Edit1:=FindComponent(‘Edit1’) as Tedit;
           你接下来应该做的就是在系统中注册这些组件类,当你为他们注册过后就能使RTTI包
    含在编译程序中并且能够被系统所使用。当你将这些类型的组件参照移到private部分时,对
    于每一个组件类,你只需为他们注册一次。即使为他们注册不是一定必要的时候,你也可以
    这样做,因为对于RegisterClasses的额外调用有益无害。通常你应该在单元中负责生成窗体
    的初始化部分添加以下的代码:
    RegisterClass([TEdit]);


    规则15:面向对象编程的窗体向导(The OOP Form. Wizard)
            为每一个窗体的每一个组件重复上述两个操作不仅十分的烦人,而且相当的浪费时间
    。为了避免额外的负担,我已经为此写了一个简单的向导程序。这个程序将会生成一些可以
    完成以上两步工作的代码,你需要做的仅仅是做几次复制和粘贴就行了。
            遗憾的是这个向导程序不能自动将代码放置到单元中合适的地方,我目前正在修改这
    个向导程序,希望能实现这个功能。你可以到我的网站(www.marcocantu.com)查找更加完
    善的程序。



    规则16:可视化窗体继承(Visual Form. Inheritance)
           如果应用得当,这将是一个强大的工具。根据我的经验,你所开发的项目越大,越能
    体现它的价值。在一个复杂的程序中,你可以使用窗体的不同等级关系来处理一组相关窗体
    的多态性(polymorphism)。
           可视化窗体继承允许你共享多个窗体的一些公共的动作:你可以使用共享的方法,公
    用的属性,甚至是事件处理程序,组件,组件属性,组件事件处理方法等等。


    规则17:限制保护域数据的使用(Limit Protected Data)
           当创建一些具有不同分级体系的类时,一些程序员趋向于主要使用保护域,因为私有
    数据不能被子类访问。我不能说这没有其合理性,但是这肯定是和封装性不相容和的。保护
    数据的实现能够被所有继承的窗体所共享,而且一旦这些数据的原始定义发生改变,你必须
    更改所有的相关部分。
           请注意,如果你遵循隐藏组件这样一条规则(Rule 14),继承窗体就不可能访问基类
    的私有组件。在一个继承窗体中,类似Edit1.Text:=’’的代码就不会被编译。虽然这是相
    当的不方便,但是至少在理论上这是值得肯定的事情,而不是否定的。如果你感觉到实现封
    装性是最主要,最需要的,就请将这些组件参照放在基类的私有段。


    规则18:保护域中的访问方法(Protected Access Methods)
            在基类中将组件参照放置在私有域中,而为这些组件添加一些访问函数来得到他们的
    属性,这将是一种更好的方法。如果这些访问函数仅仅在这些类内部使用而且不是类接口的
    一部分,你应该在保护域声明他们。例如Rule 11中描述过的GetText和SetText方法就可以声
    明成protected,并且我们可以通过调用SetText(’’)来编辑文本。
          事实上,当一个方法被镜像到一个属性时,我们可以简单地采用如下代码就可以达到编
    辑文本地目的:Text:=’’;


    规则19:保护域中的虚拟方法(Protected Virtual Methods)
            实现一个灵活的分级制度的另一个关键点是定义一些你可以从外部类调用的虚拟方法
    来得到多态性。如果这个方法使用得当,将会很少出现其他公共的方法调用保护域中的虚拟
    方法的情况。这是一个重要的技巧,因为你可以定制派生类的虚拟方法,来修改对象的动作



    规则20:用于属性的虚拟方法(Virtual Methods For Properties)
           即使是访问属性的方法也能定义成virtual,这样派生类就能改变属性的动作而不必重
    定义他们。虽然这种方法在VCL当中很少使用,但是它确实十分灵活、强大。为了实现这一点
    ,仅仅需要将Rule 11当中的Get 和Set 方法定义成Virtual。基类的代码如下所示:

    type
       TformDialog = class ( TForm)
               Procedure FormCreate(Sender:Tobject);
       Private
               Edit1:Tedit;
       Protected
               function GetText:String;virtual;
               procedure SetText(const Value:String);virtual;
       public
              constructor Create(Text :String):reintroduce;overload;
              property Text:String read GetText write SetText;
       end;
      

            在继承窗体中,你可以添加一些额外的动作来重载虚拟方法SetText:
       procedure TformInherit.SetText(const Value:String);
    begin
            inherited SetText(Value);
            if Value=’’ then
                  Button1.Enabled:=False;
           end;



    小结
             要做到一个好的Delphi面向对象编程程序员远非我在上面提到的这些规则这么简单
    。上面的这20条规则中有一些可能需要足够的耐性和时间来实现,因此,我也不能强求你能
    遵循所有的这些规则。但是这些规则应该被合适的运用到你的程序中,而且当你开发的应用
    程序越大,参与的程序员越多,这些规则越重要。不过,即使是一些小程序,始终记住这些
    规则并在合适的地方使用他们也会对你有所帮助。
            当然,还有很多其他的经验规则我没有涉及到,特别是存储器处理和RTTI问题,因为
    他们十分的复杂,需要专门的说明。
            我的结论是要遵循我上面列出的规则会付出一定的代价,特别是额外的代码,但是这
    些代价会让你得到一个更加灵活强壮的程序。希望Delphi的后续版本能够帮组我们减少这些代价。



    用Delphi编写Win2000服务程序

    一、Win2000服务简介

      服务程序(Service Application)是一种运行于WinNT的后台程序,每个服务程序(Service Application)中可能包含若干个服务(Service),每个服务就是其中的一个线程(该服务也可以创建多个子线程)。


    采用服务,应用程序可以获得特殊的权限,而且不会被用户通过Win2000的任务管理器直接结束程序,所以服务常常用来实现一些特殊的目标。

      通过Win2000控制面板中的服务管理工具,我们可以设置/查看服务的特性:

      (1)服务名称;(2)显示名称;(3)描述;(4)启动类型;(5)依赖关系;

      其中,服务名称是标识给服务的。

      以Win2000的C:\WINNT\System32\services.exe程序为例子,该Exe文件对应一个Service Application,是该服务程序的可见实体;该exe中包含多个服务(Service),例如Alerter,Dhcp(DHCP Client),Messenger等。当我们结束一个服务的时候,该服务所在的Service Application中的其他服务并没有被终止。

      在Delphi中,Borland的工程师为我们提供了TServiceApplication,TService,TServiceThread等类,封装了大量细节,简化了服务程序的开发。

    二、TServiceApplication

      在Delphi中,类TServiceApplication就对应上述的ServiceApplication。利用Delphi的开发环境,我们新建一个Service Application Project,同时就创建了一个继承自TService的类。项目文件中的Application对象就是一个TServiceApplication实例。每个TServiceApplication包含若干个TService对象,正好对应上述的服务程序和服务之间的数量关系。

      通过阅读TServiceApplication和TService类的定义,可以得知,TServiceApplication从TComponent类继承而来,TService从类TDataModule基础而来,Application对象负责各个TService对象的Create和Destroy。跟踪下列代码

      Application.CreateForm(TService1, Service1);

      可以发现创建的TService对象的Owner都是Application对象;在VCL FrameWork中Owner总是负责Destroy各个Component对象(VCL的TComponent类采用了Composite模式),所以TServiceApplication也将Destroy各个TService对象。

    下面跟踪TServiceApplication.Run的代码,可以发现TServiceApplication首先解析运行参数,实现了服务的Install和Uninstall。


    然后,初始化一个ServiceStartTable数组,该数组包含了各个service对象的服务名称和运行入口;最后创建一个TServiceStartThread 对象,该对象是一个线程对象,从线程调用API:StartServiceCtrlDispatcher来启动ServiceStartTable中指定的若干个服务;而ServiceApplication主线程就不断循环,处理消息,比如接收请求来停止/暂停某个服务。

    三、TService

      TService类继承自类 TDataModule,这意味着我们可以加入大量的VCL控件,实现丰富的功能。此外,我们还可以处理OnStart,OnPause,OnStop,OnContinue,OnCreate,OnShutDown等事件。其中需要说明的是:OnStop表示该服务被停止;而OnShutDown表示该ServiceApplication停止运行,这意味着其他服务也被终止了;两者含义是不一样的。

      前面讲过,ServiceApplication通过调用StartServiceCtrlDispatcher来启动各个服务。StartServiceCtrlDispatcher启动TService的入口,该入库就是TService.Main。TService.Main首先注册该服务,然后调用TService.DoStart。TService.DoStart创建一个内部TServiceThread成员对象,这是一个线程对象;考察TServiceThread.Execute可以得知,当我们处理的TService1. OnExecute,那么TService会把所有的请求委托给该TServiceThread成员对象处理,该对象以默认的方式处理所有的请求。

      TService. ServiceExecute是TService的主体内容。一个服务要正常运行,除了需要处理它要关注的目标(比如监听某个端口、执行某个任务等)外,还要响应外部命令/请求:比如终止、暂停、恢复该服务。因此可以考虑创建一个专门的线程来完成该任务,而在ServiceExecute中处理外面命令/请求。因此代码如下:

            while not Terminated do begin

                   ServiceThread.ProcessRequests(False);

            end;

      当然,也可以在OnExecute中处理某些任务,如监听某个端口,但是这常常会导致该Service不能及时响应Stop/Pause等请求。当OnExecute执行完了,该服务实际上就完成了任务要结束了(terminate)。


    测试string 类型和 pchar 的区别


    string和Char数组都是一块内存, 其中存放连续的字符. string保存具体字符的内存对用户
    是透明的, 由Delphi管理它的分配, 复制和释放, 用户不能干预(其实也可以, 不过是通过
    非法途径). Char数组就不必说了吧?
    PChar是一个指针, 它的大小只有32位. 定义时由Delphi自动填0. 要将PChar作为字符串
    使用的话必须自己分配内存用完必须自己释放. PChar型字符串由#0表示字符串结尾
    Delphi所提供的相关PChar字符串的操作都是判断#0来决定字符串的结尾的。
    因为PChar是指针,所以它能指向任何地方(也就是说它不一定非要指向字符串不可).
    把一个String赋值给PChar只是将String中保存具体字符串的内存的地址给PChar
    变量. 当然也可以把Char数组第一个元素的地址给PChar.
    至于 哪个占用内存小, Char数组<PChar(指分配过字符串的)<string(除了具体字符串外
    还 包含字符串长度)
    如果空字符串那么PChar<String<array [0..n] of Char
    从速度来说毫无疑问string最慢, 例如:
    作为参数传递(非var调用时)给过程时string将整个字串的副本传递过去, PChar将指针
    本身的副本传递过去(32位), Char数组和PChar一样, 传递的是第一个元素的地址副本.
    不过就灵活性来说string最高, 而且Delphi支持的函数最多. 另外可以将String作为
    Buffer使用(因为它当中可以包含字符0).

    amo (2000-9-20 1:35:26)  
    ----------------------------------------------------------------------------------
    在Delphi2.0以后的版本中,
    string分两种,
        一种是与Pascal传统string相兼容,叫ShortString,
        它的存储结构如下:
         +---------------------+    
         | 1Byte |    字符串内容 |
         +---------------------+
         0         1 ......
          其中第一个字节为字符串的长度。
          所以ShortString所能包括的字符串长度不能大于255。
        另一种是叫长字符串AnsiString,    它就是一个指向字符串的指针,不过具体的存储有些特别。
        它的存储结构如下:
        +-----------------------+
        | 4B | 4B |    字符串内容 |
        +-----------------------+
        -8     -4     0    ......
        其中,AnsiString指向字符串第一个字符,
              在第一个字符的反方向第1到第4的4个字节表示字符串长度,第5到第8的4个字节表字符串被引用的次数。

    pchar就是纯指向字符串(#0字符结尾)的指针,与C语言中的char *是一样的。

    char数组也是指向字符串的指针,它与pchar的区别在于:
          1.char数组(均指非动态数组)一旦定义好,它的长度就固定了;
          2.char数组的地址是常量,不能另赋其它值,不能象pchar一样,
             如: sPchar:pchar; sArray1,sArray2:array[0..80]of char;
               sPChar:=sArray2; sPChar;=sArray1;
               但不能sArray2:=sArray1;
         char数组就相当于const char *  

    要说速度最快当然是纯指针操作的pchar与char数组最快啦
    所谓占内存最少,效率更高,
    不知老兄你想进行什么方面的应用,
    一般对string,pchar或char数组,不用考虑这些。

    对编程而言,如果在Delphi或C++Builder中使用,可尽量使用AnsiString,
    Borland公司对它已经进行了非常完美的内部处理,
    使用非常方便。
    如果涉及到Windows API或混合编程等,接口部分一般使用pchar。

    char数组使用的比较少了,因为多数可以用char数组的地方,
    现在比较流行的作法是定义一个ansistring, 再用setlength来设定它的长度。



    lycwg (2001-1-12 11:53:20)  
    ------------------------------------------------------------
    三、字符串string 字符数组与指向字
      符串的指针pchar的区别与联系
      这3者的基本概念相同,但有一些非常细微的差别,在编程时稍不注意就会出错,需高度重视。
      1、使用指向字符串的指针,如果不是以0结尾,运行时就会出现错误。为了避免这种错误,需要在字符串结尾人工加入0 即char(0),或用strpcopy函数在字符串结尾自动加0。
      例1: 指向字符串的指针,如果不是以0结尾,运行时会出现错误:
      {s[0]=3 s[1]='n' s[2]='e' s[3]='w'}
      var
           s:string;
                 p:pchar;
      begin
           s:='new';  
             label1.caption:=s; {new}
             label2.caption:=intTostr(integer(s[0]));{3是字符串的长度}
           p:=@s[1];{不是以0结尾,莫用pchar型指针}
           label3.caption:=strpas(p); {运行时出现错误}
      end;

      例2:在字符串结尾人工加入0即char(0),可使用指向字符串的指针
      {s[0]=4 s[1]='n' s[2]='e' s[3]='w' s[4]=0;}
      {p--&gt;'new'}
      var  
                  s:string;  
                  p:pchar;
      begin
           p:=@s[1];
           s:='new'+char(0); {以0结尾,可用pchar型指针}
           label1.caption:=strpas(p); {new}

    注意:

    procedure    GetMem(var    P:    Pointer;    Size:    Integer);   
        
       //分配动态内存   
        
       function    StrPas(const    Str:    PChar):    string;   
        
       //将PChar转换为String


    Delphi中的字符串


    用DelphI 很久了,但Delphi中的字符串还是要琢磨一番的.
    Delphi中的字符串
    一:各种字符串
      字符串是Object Pascal所有数据类型中最有用的类型。许多函数以字符串为传递参数。由于在Delphi中字符串的定义和使用有各种方式,包括Pascal中典型的字符串(String),Delphi支持的长字符串(ANSIString),类似于C语言的字符数组(Array of Char),指向字符的指针(Pchar)等。下面的文章就跟谈谈这些类型在定义和应用中的区别和注意事项。


    1. 传统的Pascal字符串
      在Pascal中,典型的字符串是一定长度的字符序列。每一字符串有一设定的长度(缺省值为255),下面是一个例子:
    Var
    Address:String;
    Code:String[50];
    Address是一长度为255的字符串,Code的最大长度为50。
    传统的Pascal字符串长度不能超过255。
    可以用字符串连接操作"+"把字符串连接在一起:
    Result:=String1+String2;


    2. Delphi中的长字符串
      Delphi除了支持传统的Pascal短字符串还支持长字符串。长字符称为ANSIString。长字符串动态分配内存,即用字符串时才分配字符串所需内存,所以其长度不受限制。在Delphi中你如果用String1:String作类型说明,则String1既可能是短字符串也可能是长字符串,这取决于编译器中$H开关的设置。默认值为$H+,代表ANSI长字符串,VCL中的组件使用ANSI长字符串。长字符串以null结束,这就说明长字符串与C语言中的以null结束的字符串完全兼容。
    可以通过SetLength函数设置字符串的最大长度:
    SetLength(String1,100);用TrimLeft,TrimRight和Trim函数分别来消除字符串开头,结尾和首尾的空白区。


    3. 类似于C的字符数组
      可以用以0为起点的数组来存储以null结束的字符串。如下定义:
    Var
    Name:Array[0..50] of Char;


    4. Pchar指针
      如果Delphi中的Exended Syntax 已经设置(缺省值),以0为起点的字符数组就和指向字符的指针Pchar完全兼容,因为以0为起点的字符数组名即指向该字符数组首字符的指针。可以将字符串直接付值给Pchar指针。例如:
    var
    P: PChar;
    begin
    P := 'Hello world';
    end;
      这样P就指向存储字符串'Hello world'并以null结束的一块内存。
    许多Windows的应用程序接口API函数要求用Pchar类型作参数。Pchar指针在使用是首先用GetMem(var P: Pointer; Size: Integer)函数申请分配内存,程序结束时用FreeMem(var P: Pointer[; Size: Integer])函数释放内存。例如:
    Var WinDir,SysDir:Pchar;
    Begin
    GetMem(WinDir,256);{为指针分配内存}
    GetWindowsDirectory(WinDir,128);{将Windows安装目录放至WinDir}
    ShowMessage('Windows directory is'+WinDir);{显示结果}
    End;


    二:字符串转换
      以上介绍了Delphi中的四类字符串的定义和使用。由于各类函数对字符串参数类型要求不一,这就需要进行字符串类型转换。


    1. 可以用StrPas将以null结束的字符串转换为Pascal短字符串。StrpCopy则完成相反的转换。


    2. 因为长字符串以null结束,所以可以用强制类型转换将长字符串转换成Pchar类型。用法是:Pchar(s),s是一个长字符串。强制类型转换返回一个指向长字符串首字符的指针,并且所指字符串以null 结束。例如:
    Var
    Caption,Message:string;
    Caption:='Hello World!';
    Mssage:='This is a test of long string';
    MessageBox(0,Pchar(Message),Pchar(Caption),MB_OK);
    小结:在使用Delphi中的字符串时,要时刻清楚该字符串的类型,以免引起混淆。在理解字符串时要把字符串与指针,内存分配联系起来,加强理解。


    ADO 方式下判断数据表是否存在


    下面构造两个可重载的函数,用于在ADO方式下判断数据库的数据表是否存在。

    函数一:
    // ------------------------------------------------------------------------------
    //
    //------------------------------------------------------------------------------
    Function   TableExist( pAdoCmd: TADOCOMMAND; pcTable : string ) : boolean ; overload ;
    var cError : string ;
    begin
       ADO_COMMAND_EXEC( pAdoCmd, 'Select top 1 from ' + pcTable , cError );
       result := ( cError = '' );
    end ;

    函数二:
    // ------------------------------------------------------------------------------
    //
    //------------------------------------------------------------------------------
    Function   TableExist( pConn:TADOConnection; pcTable : string ) : boolean ; overload ;
    var tmpFldList : TStrings ;
         nLoop : integer ;
    begin
       Result := False ;
       tmpFldList := TStringList.Create ;
       pConn.GetTableNames( tmpFldList, True ); // 包含系统表
       for nLoop := 0 to tmpFldList.Count - 1 do
       begin
           if uppercase( tmpFldList[nLoop] ) = uppercase( pcTable ) then
           begin
              Result := True ;
              break ;
           end;
       end;
       tmpFldList.Free ;
    end;


    如何访问一个进程的内存空间


    在WIN32中,每个应用程序都可“看见”4GB的线性地址空间,其中最开始的4MB和最后的2GB由操作系统保留,剩下不足2GB的空间用于应用程序私有空间。具体分配如下:0xFFFFFFFF-0xC0000000的1GB用于VxD、存储器管理和文件系统;0xBFFFFFFF-0x80000000的1GB用于共享的WIN32 DLL、存储器映射文件和共享存储区;0x7FFFFFFF-0x00400000为每个进程的WIN32专用地址;0x003FFFFF-0x00001000为MS-DOS 和 WIN16应用程序;0x00000FFF-0x00000000为防止使用空指针的4,096字节。以上都是指逻辑地址,也就是虚拟内存。

    ---- 虚拟内存通常是由固定大小的块来实现的,在WIN32中这些块称为“页”,每页大小为4,096字节。在Intel CPU结构中,通过在一个控制寄存器中设置一位来启用分页。启用分页时CPU并不能直接访问内存,对每个地址要经过一个映射进程,通过一系列称作“页表”的查找表把虚拟内存地址映射成实际内存地址。通过使用硬件地址映射和页表WIN32可使虚拟内存即有好的性能而且还提供保护。利用处理器的页映射能力,操作系统为每个进程提供独立的从逻辑地址到物理地址的映射,使每个进程的地址空间对另一个进程完全不可见。WIN32中也提供了一些访问进程内存空间的函数,但使用时要谨慎,一不小心就有可能破坏被访问的进程。本文介绍如何读另一个进程的内存,写内存与之相似,完善一下你也可以做个 FPE 之类的内存修改工具。好吧,先准备好编程利器Delphi 和参考手 册 MSDN ,Now begin!

    ReadProcessMemory 读另一个进程的内存,原形如下:BOOL ReadProcessMemory(HANDLE hProcess,          // 被读取进程的句柄;LPCVOID lpBaseAddress,       // 读的起始地址;LPVOID lpBuffer,          // 存放读取数据缓冲区;DWORD nSize,          // 一次读取的字节数;LPDWORD lpNumberOfBytesRead // 实际读取的字节数;);hProcess 进程句柄可由OpenProcess 函数得到,原形如下:HANDLE OpenProcess(DWORD dwDesiredAccess, // 访问标志;BOOL bInheritHandle,    // 继承标志;DWORD dwProcessId       // 进程ID;);


    ---- 当然,用完别忘了用 CloseHandle 关闭打开的句柄。读另一个进程的内存 dwDesiredAccess 须指定为 PROCESS_VM_READ ,写另一个进程的内存 dwDesiredAccess 须指定为 PROCESS_VM_WRITE ,继承标志无所谓,进程ID可由 Process32First 和 Process32Next 得到,这两个函数可以枚举出所有开启的进程,这样进程的信息也就得到了。 Process32First 和 Process32Next是由 TLHelp32 单元提供的,需在 uses 里加上TLHelp32。ToolsHelp32 封装了一些访问堆、线程、进程等 的函数,只适用于Win9x,原形如下:

    BOOL WINAPI Process32First(HANDLE hSnapshot       //由 CreateToolhelp32Snapshot 返回的系统快照句柄;LPPROCESSENTRY32 lppe // 指向一个 PROCESSENTRY32 结构;);BOOL WINAPI Process32Next(HANDLE hSnapshot       // 由 CreateToolhelp32Snapshot 返回的系统快照句柄;LPPROCESSENTRY32 lppe // 指向一个 PROCESSENTRY32 结构;);hSnapshot 由 CreateToolhelp32Snapshot 返回的系统快照句柄;CreateToolhelp32Snapshot 原形如下:HANDLE WINAPI CreateToolhelp32Snapshot(DWORD dwFlags,       // 快照标志;DWORD th32ProcessID // 进程ID;);现在需要的是进程的信息,所以将 dwFlags指定为 TH32CS_SNAPPROCESS,th32ProcessID 忽略;PROCESSENTRY32 结构如下:typedef struct tagPROCESSENTRY32 {DWORD dwSize;              // 结构大小;DWORD cntUsage;            // 此进程的引用计数;DWORD th32ProcessID;       // 进程ID;DWORD th32DefaultHeapID;   // 进程默认堆ID;DWORD th32ModuleID;        // 进程模块ID;DWORD cntThreads;          // 此进程开启的线程计数;DWORD th32ParentProcessID;// 父进程ID;LONG   pcPriClassBase;      // 线程优先权;DWORD dwFlags;             // 保留;char szExeFile[MAX_PATH]; // 进程全名;} PROCESSENTRY32;


    ---- 至此,所用到的主要函数已介绍完,实现读内存只要从下到上依次调用上述函数即可,具体参见原代码:

    procedure TForm1.Button1Click(Sender: TObject);varFSnapshotHandle:THandle;FProcessEntry32:TProcessEntry32;Ret : BOOL;ProcessID : integer;ProcessHndle : THandle;lpBuffer:pByte;nSize: DWORD;lpNumberOfBytesRead: DWORD;i:integer;s:string;beginFSnapshotHandle:=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);//创建系统快照FProcessEntry32.dwSize:=Sizeof(FProcessEntry32);//先初始化 FProcessEntry32 的大小Ret:=Process32First(FSnapshotHandle,FProcessEntry32);while Ret dobegins:=ExtractFileName(FProcessEntry32.szExeFile);if s='KERNEL32.DLL' thenbeginProcessID:=FProcessEntry32.th32ProcessID;s:='';break;end;Ret:=Process32Next(FSnapshotHandle,FProcessEntry32);end;//循环枚举出系统开启的所有进程,找出“Kernel32.dll”CloseHandle(FSnapshotHandle);Memo1.Lines.Clear ;memo1.lines.add('Process ID '+IntToHex(FProcessEntry32.th32ProcessID,8));memo1.lines.Add('File name '+FProcessEntry32.szExeFile);////输出进程的一些信息nSize:=4;lpBuffer:=AllocMem(nSize);ProcessHndle:=OpenProcess(PROCESS_VM_READ,false,ProcessID);memo1.Lines.Add ('Process Handle '+intTohex(ProcessHndle,8));for i:=$00800001 to $0080005f dobeginReadProcessMemory(ProcessHndle,Pointer(i),lpBuffer,nSize,lpNumberOfBytesRead);s:=s+intTohex(lpBuffer^,2)+' ';//读取内容if (i mod 16) =0 thenbeginMemo1.Lines.Add(s);s:='';end;//格式化输出end;FreeMem(lpBuffer,nSize);CloseHandle(ProcessHndle);//关闭句柄,释放内存end;


    ---- 以上程序在 Delphi4 中文Win98 下调试通过。


    该文章转载自网络大本营:http://www.xrss.cn/Dev/Delphi/200773115324.Html

    总算把我做了不到2年delphi开发工程师那时候一些比较记忆深的文章和随笔从我的一个blog合并到这里面了,感谢大家的支持 呵呵,大家一起努力,GT 万岁!!!


  • delphi 开发和测试2004-2006年总结经验【四】

    2007-01-25 15:33:00

    怎样得到CPU的序列号

    unit
    Main;

    interface

    uses   Windows,   Messages,   SysUtils,   Classes,   Graphics,   Controls,
    Forms,   Dialogs,   ExtCtrls,   StdCtrls,   Buttons;

    type
    TDemoForm. =
    class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    GetButton: TBitBtn;
    CloseButton: TBitBtn;
    Bevel1: TBevel;
    Label5: TLabel;
    FLabel: TLabel;
    MLabel: TLabel;
    PLabel: TLabel;
    SLabel: TLabel;
    PValue: TLabel;
    FValue: TLabel;
    MValue: TLabel;
    SValue: TLabel;
    procedure GetButtonClick(Sender: TObject);
    end;

    var
    DemoForm. TDemoForm;

    implementation

    {$R *.DFM}

    const
    ID_BIT = $200000;   
    // EFLAGS ID bit
    type
    TCPUID =
    array[1..4] of Longint;
    TVendor =
    array [0..11] of char;

    function IsCPUID_Available : Boolean; register;
    asm
       PUSHFD       
    {direct access to flags no possible, only via stack}
       POP      EAX     
    {flags to EAX}
       MOV      EDX,EAX   
    {save current flags}
      
    XOR      EAX,ID_BIT {not ID bit}
       PUSH     EAX     
    {onto stack}
       POPFD        
    {from stack to flags, with not ID bit}
       PUSHFD       
    {back to stack}
       POP      EAX     
    {get back to EAX}
      
    XOR      EAX,EDX   {check if ID bit affected}
       JZ       @exit    
    {no, CPUID not availavle}
       MOV      AL,
    True   {Result=True}
       @exit:
    end;

    function GetCPUID : TCPUID; assembler; register;
    asm
       PUSH     EBX         
    {Save affected register}
       PUSH     EDI
       MOV      EDI,EAX     
    {@Resukt}
       MOV      EAX,1
       DW       $A20F       
    {CPUID Command}
       STOSD             
    {CPUID[1]}
       MOV      EAX,EBX
       STOSD               
    {CPUID[2]}
       MOV      EAX,ECX
       STOSD               
    {CPUID[3]}
       MOV      EAX,EDX
       STOSD               
    {CPUID[4]}
       POP      EDI     
    {Restore registers}
       POP      EBX
    end;

    function GetCPUVendor : TVendor; assembler; register;
    asm
       PUSH     EBX     
    {Save affected register}
       PUSH     EDI
       MOV      EDI,EAX   
    {@Result (TVendor)}
       MOV      EAX,0
       DW       $A20F    
    {CPUID Command}
       MOV      EAX,EBX
       XCHG   EBX,ECX     
    {save ECX result}
       MOV    ECX,4
       @1:
       STOSB
      
    SHR      EAX,8
       LOOP     @1
       MOV      EAX,EDX
       MOV    ECX,4
       @2:
       STOSB
      
    SHR      EAX,8
       LOOP     @2
       MOV      EAX,EBX
       MOV    ECX,4
       @3:
       STOSB
      
    SHR      EAX,8
       LOOP     @3
       POP      EDI     
    {Restore registers}
       POP      EBX
    end;

    procedure TDemoForm.GetButtonClick(Sender: TObject);
    var
    CPUID : TCPUID;
    I      : Integer;
    S    : TVendor;
    begin
      
    for I := Low(CPUID) to High(CPUID)  do CPUID[I] := -1;
      
    if IsCPUID_Available then begin
         CPUID := GetCPUID;
         Label1.Caption := 'CPUID[1] = ' + IntToHex(CPUID[1],8);
         Label2.Caption := 'CPUID[2] = ' + IntToHex(CPUID[2],8);
         Label3.Caption := 'CPUID[3] = ' + IntToHex(CPUID[3],8);
         Label4.Caption := 'CPUID[4] = ' + IntToHex(CPUID[4],8);
         PValue.Caption := IntToStr(CPUID[1]
    shr 12 and 3);
         FValue.Caption := IntToStr(CPUID[1]
    shr 8 and $f);
         MValue.Caption := IntToStr(CPUID[1]
    shr 4 and $f);
         SValue.Caption := IntToStr(CPUID[1]
    and $f);
         S := GetCPUVendor;
         Label5.Caption := 'Vendor: ' + S;
    end
      
    else begin
         Label5.Caption := 'CPUID not available';
      
    end;
    end;

    end.

    用Delphi实现文件下载的几种方法

    uses UrlMon;
    function DownloadFile(Source, Dest: string): Boolean;
    begin
      
    try
         Result := UrlDownloadToFile(
    nil, PChar(source), PChar(Dest), 0, nil) = 0;
        
    except
           Result :=
    False;
        
    end;
      
    end;
      
      
    if DownloadFile('http://www.borland.com/delphi6.zip, 'c:\kylix.zip') then
    ShowMessage('Download succesful')
    else ShowMessage('Download unsuccesful')

    *************************************************************************************************

    Uses URLMon, ShellApi;
    function DownloadFile(SourceFile, DestFile: string): Boolean;
    begin
    try
    Result := UrlDownloadToFile(nil, PChar(SourceFile), PChar(DestFile), 0, nil) = 0;
    except
    Result := False;
    end;
    end;

    procedure TForm1.Button1.Click(Sender: TObject);
    const
    // URL Location
    SourceFile := 'http:
    //www.google.com/intl/de/images/home_title.gif';
    // Where to save the file
    DestFile := 'c:\temp\google-image.gif';
    begin
      
    if DownloadFile(SourceFile, DestFile) then
      
    begin
         ShowMessage('Download succesful!');
        
    // Show downloaded image in your browser
    ShellExecute(Application.Handle,PChar('open'),PChar(DestFile),PChar(''),nil,SW_NORMAL)
      
    end
      
    else
       ShowMessage('Error while downloading ' + SourceFile)
    end;

    *************************************************************************************************

    NMHTTP1.InputFileMode := ture;
    NMHTTP1.Body := '本地文件名';
    NMHTTP1.Header := 'Head.txt';
    NMHTTP1.OutputFileMode :=
    FALSE;
    NMHTTP1.ReportLevel := Status_Basic;
    NMHTTP1.Proxy := '代理服务器的IP地址';
    NMHTTP1.ProxyPort := '代理服务器的端口号';
    With NMHTTP1.HeaderInfo do
      
      
    Begin
         Cookie := '';
         LocalMailAddress := '';
         LocalProgram := '';
         Referer := '';
         UserID := '用户名称';
         Password := '用户口令';
        
    End;
        
         NMHTTP1.Get(‘http:
    //www.abcdefg.com/software/a.zip');

    *************************************************************************************************

    uses URLMon;

    ...

    OleCheck(URLDownloadToFile(
    nil,'URL','Filename',0,nil));

    *************************************************************************************************

    var
    DownLoadFile:TFileStream;
    beginio
    DownLoadFile:=TFileStream.Create('c:\aa.rar',fmCreate);
    IdHTTP1.Get('http://www.sina.com.cn/download/aa.rar',DownLoadFile);
    DownLoadFile.Free;
    end;

    *************************************************************************************************

    创建具有托盘的服务程序

    Windows 2000/XP和2003等支持一种叫做"服务程序"的东西.程序作为服务启动有以下几个好处:

         (1)不用登陆进系统即可运行.
         (2)具有SYSTEM特权.所以你在进程管理器里面是无法结束它的.

         笔者在2003年为一公司开发机顶盒项目的时候,曾经写过课件上传和媒体服务,下面就介绍一下如何用Delphi7创建一个Service程序.
         运行Delphi7,选择菜单File-->New-->Other--->Service Application.将生成一个服务程序的框架.将工程保存为ServiceDemo.dpr和Unit_Main.pas,然后回到主框架.我们注意到,Service有几个属性.其中以下几个是我们比较常用的:

         (1)DisplayName:服务的显示名称
         (2)Name:服务名称.

         我们在这里将DisplayName的值改为"Delphi服务演示程序",Name改为"DelphiService".编译这个项目,将得到ServiceDemo.exe.这已经是一个服务程序了!进入CMD模式,切换致工程所在目录,运行命令"ServiceDemo.exe /install",将提示服务安装成功!然后"net start DelphiService"将启动这个服务.进入控制面版-->管理工具-->服务,将显示这个服务和当前状态.不过这个服务现在什么也干不了,因为我们还没有写代码:)先"net stop DelphiService"停止再"ServiceDemo.exe /uninstall"删除这个服务.回到Delphi7的IDE.

         我们的计划是为这个服务添加一个主窗口,运行后任务栏显示程序的图标,双击图标将显示主窗口,上面有一个按钮,点击该按钮将实现Ctrl+Alt+Del功能.

         实际上,服务程序莫认是工作于Winlogon桌面的,可以打开控制面板,查看我们刚才那个服务的属性-->登陆,其中"允许服务与桌面交互"是不打钩的.怎么办?呵呵,回到IDE,注意那个布尔属性:Interactive,当这个属性为True的时候,该服务程序就可以与桌面交互了.

         File-->New-->Form为服务添加窗口FrmMain,单元保存为Unit_FrmMain,并且把这个窗口设置为手工创建.完成后的代码如下:



    unit Unit_Main;

    interface

    uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs,
    Unit_FrmMain;

    type
    TDelphiService =
    class(TService)
    procedure ServiceContinue(Sender: TService; var Continued: Boolean);
    procedure ServiceExecute(Sender: TService);
    procedure ServicePause(Sender: TService; var Paused: Boolean);
    procedure ServiceShutdown(Sender: TService);
    procedure ServiceStart(Sender: TService; var Started: Boolean);
    procedure ServiceStop(Sender: TService; var Stopped: Boolean);
    private
    { Private declarations }
    public
    function GetServiceController: TServiceController; override;
    { Public declarations }
    end;

    var
    DelphiService: TDelphiService;
    FrmMain: TFrmMain;
    implementation

    {$R *.DFM}

    procedure ServiceController(CtrlCode: DWord); stdcall;
    begin
       DelphiService.Controller(CtrlCode);
    end;

    function TDelphiService.GetServiceController: TServiceController;
    begin
       Result := ServiceController;
    end;

    procedure TDelphiService.ServiceContinue(Sender: TService;
    var Continued: Boolean);
    begin
      
    while not Terminated do
      
    begin
         Sleep(10);
         ServiceThread.ProcessRequests(
    False);
      
    end;
    end;

    procedure TDelphiService.ServiceExecute(Sender: TService);
    begin
      
    while not Terminated do
      
    begin
         Sleep(10);
         ServiceThread.ProcessRequests(
    False);
      
    end;
    end;

    procedure TDelphiService.ServicePause(Sender: TService;
    var Paused: Boolean);
    begin
       Paused :=
    True;
    end;

    procedure TDelphiService.ServiceShutdown(Sender: TService);
    begin
       gbCanClose :=
    true;
       FrmMain.Free;
       Status := csStopped;
       ReportStatus();
    end;

    procedure TDelphiService.ServiceStart(Sender: TService;
    var Started: Boolean);
    begin
       Started :=
    True;
       Svcmgr.Application.CreateForm(TFrmMain, FrmMain);
       gbCanClose :=
    False;
       FrmMain.Hide;
    end;

    procedure TDelphiService.ServiceStop(Sender: TService;
    var Stopped: Boolean);
    begin
       Stopped :=
    True;
       gbCanClose :=
    True;
       FrmMain.Free;
    end;

    end.



    主窗口单元如下:


    unit Unit_FrmMain;

    interface

    uses
    Windows, Messages, SysUtils, Variants, Classes, ShellApi, Graphics, Controls, Forms,
    Dialogs, ExtCtrls, StdCtrls;

    const
    WM_TrayIcon = WM_USER + 1234;
    type
    TFrmMain =
    class(TForm)
    Timer1: TTimer;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormDestroy(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    private
    { Private declarations }
    IconData: TNotifyIconData;
    procedure AddIconToTray;
    procedure DelIconFromTray;
    procedure TrayIconMessage(var Msg: TMessage); message WM_TrayIcon;
    procedure SysButtonMsg(var Msg: TMessage); message WM_SYSCOMMAND;
    public
    { Public declarations }
    end;

    var
    FrmMain: TFrmMain;
    gbCanClose: Boolean;
    implementation

    {$R *.dfm}

    procedure TFrmMain.FormCreate(Sender: TObject);
    begin
       FormStyle.:= fsStayOnTop;
    {窗口最前}
       SetWindowLong(Application.Handle, GWL_EXSTYLE, WS_EX_TOOLWINDOW);
    {不在任务栏显示}
       gbCanClose :=
    False;
       Timer1.Interval := 1000;
       Timer1.Enabled :=
    True;
    end;

    procedure TFrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    begin
       CanClose := gbCanClose;
      
    if not CanClose then
      
    begin
         Hide;
      
    end;
    end;

    procedure TFrmMain.FormDestroy(Sender: TObject);
    begin
       Timer1.Enabled :=
    False;
       DelIconFromTray;
    end;

    procedure TFrmMain.AddIconToTray;
    begin
       ZeroMemory(@IconData, SizeOf(TNotifyIconData));
       IconData.cbSize := SizeOf(TNotifyIconData);
       IconData.Wnd := Handle;
       IconData.uID := 1;
       IconData.uFlags := NIF_
    MESSAGE or NIF_ICON or NIF_TIP;
       IconData.uCallbackMessage := WM_TrayIcon;
       IconData.hIcon := Application.Icon.Handle;
       IconData.szTip := 'Delphi服务演示程序';
       Shell_NotifyIcon(NIM_ADD, @IconData);
    end;

    procedure TFrmMain.DelIconFromTray;
    begin
       Shell_NotifyIcon(NIM_DELETE, @IconData);
    end;

    procedure TFrmMain.SysButtonMsg(var Msg: TMessage);
    begin
      
    if (Msg.wParam = SC_CLOSE) or
       (Msg.wParam = SC_MINIMIZE)
    then Hide
      
    else inherited; // 执行默认动作
    end;

    procedure TFrmMain.TrayIconMessage(var Msg: TMessage);
    begin
      
    if (Msg.LParam = WM_LBUTTONDBLCLK) then Show();
    end;

    procedure TFrmMain.Timer1Timer(Sender: TObject);
    begin
       AddIconToTray;
    end;

    procedure SendHokKey;stdcall;
    var
    HDesk_WL: HDESK;
    begin
       HDesk_WL := OpenDesktop ('Winlogon', 0,
    False, DESKTOP_JOURNALPLAYBACK);
      
    if (HDesk_WL <> 0) then
      
    if (SetThreadDesktop (HDesk_WL) = True) then
       PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, MAKELONG (
    MOD_ALT or MOD_CONTROL, VK_DELETE));
    end;

    procedure TFrmMain.Button1Click(Sender: TObject);
    var
    dwThreadID : DWORD;
    begin
       CreateThread(
    nil, 0, @SendHokKey, nil, 0, dwThreadID);
    end;

    end.

    program ServiceDemo;

    uses
    SvcMgr,
    Unit_Main in 'Unit_Main.pas' {DelphiService: TService},
    Unit_frmMain in 'Unit_frmMain.pas' {frmMain};

    {$R *.RES}

    begin
       Application.Initialize;
       Application.CreateForm(TDelphiService, DelphiService);
       Application.Run;
    end.




    窗体代码如下:


    object DelphiService: TDelphiService
    OldCreateOrder =
    False
    DisplayName = 'Delphi服务演示程序'
    Interactive =
    True
    OnContinue = ServiceContinue
    OnExecute = ServiceExecute
    OnPause = ServicePause
    OnShutdown = ServiceShutdown
    OnStart = ServiceStart
    OnStop = ServiceStop
    Left = 261
    Top = 177
    Height = 150
    Width = 215
    end

    object frmMain: TfrmMain
    Left = 192
    Top = 107
    Width = 696
    Height = 480
    Caption = '我的服务测试程序'
    Color = clBtnFace
    Font.Charset =
    DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style. = []
    OldCreateOrder =
    False
    OnCloseQuery = FormCloseQuery
    OnCreate = FormCreate
    OnDestroy = FormDestroy
    PixelsPerInch = 96
    TextHeight = 13
    object Button1: TButton
    Left = 296
    Top = 264
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
    end
    object Timer1: TTimer
    OnTimer = Timer1Timer
    Left = 120
    Top = 192
    end
    end



    让TreeView前面显示CheckBox

    方法一:

    SetWindowLong(TreeView1.Handle, GWL_STYLE, GetWindowLong(TreeView1.Handle, GWL_STYLE) or $00000100);

    方法二:

    TreeView不需要图片即可实现CheckBoxes
    //代码如下,至于其它点击事件的处理,应该比较简单

    unit Unit1;

    interface

    uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
    Dialogs, CommCtrl, StdCtrls, ComCtrls;

    type
    TForm1 = class(TForm)
    TreeView1: TTreeView;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    private
    { Private declarations }
    function IsNodeChecked(Node :TTreeNode) :Boolean;
    public
    { Public declarations }
    end;

    const
    TVIS_CHECKED = $2000;

    var
    Form1: TForm1;

    implementation

    {$R *.dfm}

    procedure TForm1.Button1Click(Sender: TObject);
    begin
    if (TreeView1.Selected = nil) then Exit;

    ShowMessage('Node checked? ' + BoolToStr(IsNodeChecked(TreeView1.Selected), True));
    end;

    procedure TForm1.FormCreate(Sender: TObject);
    var
    dw: DWORD;
    begin
    //给TreeView加上CheckBoxes
    dw := GetWindowLong( TreeView1.Handle, GWL_STYLE);
    dw := dw or TVS_CHECKBOXES;
    SetWindowLong(TreeView1.Handle, GWL_STYLE. , dw);
    end;

    procedure TForm1.Button2Click(Sender: TObject);
    //设置node的check状态
    procedure SetNodeChecked(Node :TTreeNode; Checked :Boolean);
    var
    TvItem :TTVItem;
    begin
    FillChar(TvItem, SizeOf(TvItem), 0);
    with TvItem do begin
    hItem := Node.ItemId;
    Mask := TVIF_STATE;
    StateMask := TVIS_STATEIMAGEMASK;
    if Checked then
    TvItem.State :=TVIS_CHECKED
    else
    TvItem.State :=TVIS_CHECKED shr 1;
    TreeView_SetItem(Node.TreeView.Handle, TvItem);
    end;
    end;
    begin
    if (TreeView1.Selected = nil) then Exit;

    SetNodeChecked(TreeView1.Selected, not IsNodeChecked(TreeView1.Selected));
    end;

    //检查Node是否Checked
    function TForm1.IsNodeChecked(Node: TTreeNode): Boolean;
    var
    TvItem :TTVItem;
    begin
    TvItem.Mask := TVIF_STATE;
    TvItem.hItem := Node.ItemId;
    TreeView_GetItem(Node.TreeView.Handle, TvItem);
    Result := (TvItem.State and TVIS_CHECKED) = TVIS_CHECKED;
    end;

    end.



  • delphi 开发和测试2004-2006年总结经验【三】

    2007-01-25 15:19:38

    如何把文件删除到回收站中

    关键词:删除文件 回收站
    program del;
    uses ShellApi;
    { 利用ShellApi中: function SHFileOperation(const lpFileOp: TSHFileOpStruct): Integer; stdcall; }
    Var T:TSHFileOpStruct;
    P:String;
    begin
    P:='C:\Windows\System\EL_CONTROL.CPL';
    With T do
    Begin
    Wnd:=0;
    wFunc:=FO_DELETE;
    pFrom:=Pchar(P);
    fFlags:=FOF_ALLOWUNDO
    End;
    SHFileOperation(T);
    End.

    注意:
    1. 给出文件的绝对路径名,否则可能不能恢复;
    2. MS的文档说对于多个文件,每个文件名必须被#)字符分隔,而整个字符串必须用两个#0结束。

    实现打开或运行一个指定文件

    关键词:打开文件 运行文件 ShellExecute 打开网页
    打开Windows已经注册的文件其实很简单,根据以下代码定义一个过程:
    procedure URLink(URL:PChar);
    begin
    ShellExecute(0, nil, URL, nil, nil, SW_NORMAL);
    end;
    在要调用的地方使用
    URLink('Readme.txt');
    如果是链接主页的话,那么改用
    URLink('http://gui.yeah.net');

    查找一个目录下的某些特定的文件

    关键词:搜索文件 查找文件 检索文件
    方法如下:
    FileSearch :查找目录中是否存在某一特定文件
    FindFirst :在目录中查找与给定文件名(可以包含匹配符)及属性集相匹配的第一个文件
    FindNext :返回符合条件的下一个文件
    FindClose :中止一个FindFirst / FindNext序列

    //参数:
    //Directory : string 目录路径
    //RetList : TStringList 包含了目录路径和查询到的文件

    Function TForm1.FindAllFileInADirectory(Directory:string;RetList : TStringList):Boolean;
    var
    SearchRec: TSearchRec;
    begin
       if FindFirst(Directory+'*.*', faAnyFile, SearchRec) = 0 then
       begin
         repeat
         RetList.Add(Directory +'   '+ SearchRec.Name);
         until (FindNext(SearchRec) <> 0);
       end;
       FindClose(SearchRec);
    end;

    Delphi中关于文件、目录操作的函数

    关键词:文件、目录操作
    //关于文件、目录操作
    Chdir('c:\abcdir'); // 转到目录
    Mkdir('dirname'); //建立目录
    Rmdir('dirname'); //删除目录
    GetCurrentDir; //取当前目录名,无'\'
    Getdir(0,s); //取工作目录名s:='c:\abcdir';
    Deletfile('abc.txt'); //删除文件
    Renamefile('old.txt','new.txt'); //文件更名
    ExtractFilename(filelistbox1.filename); //取文件名
    ExtractFileExt(filelistbox1.filename); //取文件后缀

    如何判断一个文件是不是正在被使用

    关键词:文件状态
    function IsFileInUse(FileName: TFileName): Boolean;
    var
    HFileRes: HFILE;
    begin
    Result := False;
    if not FileExists(FileName) then Exit;
    HFileRes := CreateFile(PChar(FileName),
    GENERIC_READ or GENERIC_WRITE,
    0,
    nil,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    0);
    Result := (HFileRes = INVALID_HANDLE_VALUE);
    if not Result then
    CloseHandle(HFileRes);
    end;


    检查文件是否为文本文件

    关键词:文本文件
    Function isAscii(Nomefile: String): Boolean;
    const
    Sett=2048;
    var
    i: Integer;
    F: file;
    a: Boolean;
    TotSize, IncSize, ReadSize: Integer;
    c: Array[0..Sett] of byte;
    begin
    If FileExists(NomeFile) then
    begin
    {$I-}
    AssignFile(F, NomeFile);
    Reset(F, 1);
    TotSize:=FileSize(F);
    IncSize:=0;
    a:=true;
    while (IncSize<TotSize) and (a=true) do
    begin

    ReadSize:=Sett;

    If IncSize+ReadSize>TotSize then ReadSize:=TotSize-IncSize;

    IncSize:=IncSize+ReadSize;

    BlockRead(F, c, ReadSize);

    For i := 0 to ReadSize-1 do // Iterate

    If (c[i]<32) and (not (c[i] in [9, 10, 13, 26])) then a:=False;

    end; // while

    CloseFile(F);

    {$I+}

    If IOResult<>0 then Result:=False

    else Result:=a;

    end;

    end;

    procedure TForm1.Button1Click(Sender: TObject);

    begin

    if OpenDialog1.Execute then

    begin

    if isAscii(OpenDialog1.FileName) then

    begin

    ShowMessage('ASCII File');

    end;

    end;

    end;


    查找所有文件

    关键词:查找所有文件
    procedure findall(disk,path: String; var fileresult: Tstrings);
    var

    fpath: String;

    fs: TsearchRec;

    begin

    fpath:=disk+path+'\*.*';

    if findfirst(fpath,faAnyFile,fs)=0 then

    begin

    if (fs.Name<>'.')and(fs.Name<>'..') then

    if (fs.Attr and faDirectory)=faDirectory then

    findall(disk,path+'\'+fs.Name,fileresult)

    else

    fileresult.add(disk+strpas(strupper(pchar(path)))+'\'+strpas(

    strupper(pchar(fs.Name)))+'('+inttostr(fs.Size)+')');

    while findnext(fs)=0 do

    begin

    if (fs.Name<>'.')and(fs.Name<>'..') then

    if (fs.Attr and faDirectory)=faDirectory then

    findall(disk,path+'\'+fs.Name,fileresult)

    else

    fileresult.add(disk+strpas(strupper(pchar(path)))+'\'+str

    pas(strupper(pchar(fs.Name)))+'('+inttostr(fs.Size)+')');

    end;

    end;

    findclose(fs);

    end;

    procedure DoSearchFile(Path: string; Files: TStrings = nil);

    var

    Info: TSearchRec;

    procedure ProcessAFile(FileName: string);

    begin

    if Assigned(PnlPanel) then

    PnlPanel.Caption := FileName;

    Label2.Caption := FileName;

    end;

    function IsDir: Boolean;

    begin

    with Info do

    Result := (Name <> '.') and (Name <> '..') and ((attr and fadirectory) = fadirectory);

    end;

    function IsFile: Boolean;

    begin

    Result := not ((Info.Attr and faDirectory) = faDirectory);

    end;

    begin

    Path := IncludeTrailingBackslash(Path);

    try

    if FindFirst(Path + '*.*', faAnyFile, Info) = 0 then

    if IsFile then

    ProcessAFile(Path + Info.Name)

    else if IsDir then DoSearchFile(Path + Info.Name);

    while FindNext(Info) = 0 do

    begin

    if IsDir then

    DoSearchFile(Path + Info.Name)

    else if IsFile then

    ProcessAFile(Path + Info.Name);

    Application.ProcessMessages;

    if QuitFlag then Break;

    Sleep(100);

    end;

    finally

    FindClose(Info);

    end;

    end;


    用DELPHI实现文件加密压缩

    关键词:加密压缩、Zlib、流、资源文件
    概述:
    在这篇笔记中,讲述对单个文件的数据加密、数据压缩、自解压的实现。同样,也可以实现对多个文件或文件夹的压缩,只要稍加修改便可实现。

    关键字:加密压缩、Zlib、流、资源文件

    引 言:
    在 日常中,我们一定使用过WINZIP、WINRAR这样的出名的压缩软件,就是我们开发软件过程中不免要遇到数据加密、数据压缩的问题!本文中就这一技术 问题展开探讨,同时感谢各位网友的技巧,在我每次面对问题要解决的时候,是你们辛苦地摸索出来的技巧总是让我豁然开朗,问题迎刃而解。本篇文章主要是运用 DELPH的强大的流处理方面的技巧来实现的数据加密压缩,并用于实际的软件程序开发中,将我个人的心得、开发经验写出来与大家分享。

    1、 系统功能
    1)、数据压缩
    使用DELPHI提供的两个流类(TCompressionStream和TDecompressionStream)来完成数据的压缩和解压缩。
    2)、数据加密压缩
    通过Delphi编程中“流”的应用实现数据加密,主要采用Tstream的两个派生类Tfilestream、Tmemorystream 来完成的;其中数据压缩部分采用1)的实现方法
    3)、双击压缩文件自动关联解压
    通过更改注册表的实现扩展名与程序文件的关联,主要采用Tregistry;并且,API函数SHChangeNotify实现注册效果的立即呈现。
    4)、可生成自解压文件
    自解压的文件实现数据压缩1)与数据加密压缩2)的自动解压;并且,通过资源文件的使用实现可执行的自解压文件与数据文件的合并,来完成数据的自解压实现。

    2、 系统实现
    2.1、工作原理















    2.2、关键技术的讲述
    (一)ZLIB
    1)、基类 TCustomZlibStream:是类TCompressionStream和TDecompressionStream 类的基类,它主要有一个属性: OnProgress,在类进行压缩或解压缩的过程中会发生这个的事件 。
    格式:Procedure OnProgress (Sender: TObject); dynamic;
    2)、压缩类TCompressionStream:除了继承了基类的OnProgress 属性外,又增加了一个属性:CompressionRate,它的定义如下:
    Property CompressionRate: Single read GetCompressionRate;
    通过这个属性,可以得到压缩比。
    它的几个重要的方法定义如下:
    Constructor TCompressionStream.Create (CompressionLevel: TCompressionLevel; Dest: TStream);
    其中:TcompressionLevel(压缩类型),它由如下几个定义:
       1)、 clNone :不进行数据压缩;
       2)、 clFastest:进行快速压缩,牺牲压缩效率;
       3)、 clDefault:进行正常压缩;
       4)、 clMax: 进行最大化压缩,牺牲速度;
    Dest:目的流,用于存放压缩过的数据。
    Function TCompressionStream.Write (const Buffer; Count: Longint): Longint;
    其中:Buffer:需要压缩的数据;
       Count: 需要压缩的数据的字节数;
    函数返回写入流的字节数。
    注 意:压缩类TCompressionStream的数据只能是写入的,如果试图从其内部读取数据,将发生一个"Error "异常。需要压缩的数据通过方法 Write写入流中,在写入的过程中就被压缩,并保存在由构造函数提供的内存流(TmemoryStream)中,同时触发 OnProcess 事件。
    3)、 解压缩类 TDecompressionStream :和压缩类TcompressionStream相反,它的数据是只能读出的,如果试图往其内部写数据,将发生一个"Error "异常。
    它的几个重要方法定义如下:
    构造函数:Constructor Create(Source: TStream);
      其中:Source 是保存着压缩数据的流;
    Function Read(var Buffer; Count: Longint): Longint;
      数据读出函数,Buffer: 存数据缓冲区;Count: 缓冲区的大小;
      函数返回读出的字节数。数据在读出的过程中,数据被解压缩,并触发 OnProcess 事件。



    (二)流
    在Delphi中,所有流对象的基类为TStream类,其中定义了所有流的共同属性和方法。
    TStream类中定义的属性如下:
    1)、Size:此属性以字节返回流中数据大小。
    2)、Position:此属性控制流中存取指针的位置。

    Tstream中定义的虚方法有四个:
    1)、Read:此方法实现将数据从流中读出,返回值为实际读出的字节数,它可以小于或等于指定的值。
    2)、Write:此方法实现将数据写入流中,返回值为实际写入流中的字节数。
    3)、Seek:此方法实现流中读取指针的移动,返回值为移动后指针的位置。
    函数原形为:Function Seek(Offset:Longint;Origint:Word):Longint;virtual;abstract;
    参数Offset为偏移字节数,参数Origint指出Offset的实际意义,其可能的取值如下:
    soFromBeginning:Offset为指针距离数据开始的位置。此时Offset必须大于或者等于零。
    soFromCurrent:Offset为移动后指针与当前指针的相对位置。
    soFromEnd:Offset为移动后指针距离数据结束的位置。此时Offset必须小于或者等于零。
    4)、Setsize:此方法实现改变数据的大小。

    另外,TStream类中还定义了几个静态方法:
    1)、ReadBuffer:此方法的作用是从流中当前位置读取数据,跟上面的Read相同。
    注意:当读取的数据字节数与需要读取的字节数不相同时,将产生EReadError异常。
    2)、WriteBuffer:此方法的作用是在当前位置向流写入数据,跟上面的Write相同。
    注意:当写入的数据字节数与需要写入的字节数不相同时,将产生EWriteError异常。
    3)、CopyFrom:此方法的作用是从其它流中拷贝数据流。
    函数原形为:Function CopyFrom(Source:TStream;Count:Longint):Longint;
    参 数Source为提供数据的流,Count为拷贝的数据字节数。当Count大于0时,CopyFrom从Source参数的当前位置拷贝Count个字 节的数据;当Count等于0时,CopyFrom设置Source参数的Position属性为0,然后拷贝Source的所有数据;

    Tstream常见派生类:
    TFileStream (文件流的存取)
    TStringStream (处理内存中的字符串类型数据)
    TmemoryStream (对于工作的内存区域数据处理)
    TBlobStream (BLOB类型字段的数据处理)
    TwinSocketStream (socket的读写处理)
    ToleStream (COM接口的数据处理)
    TresourceStream (资源文件流的处理)
    其中最常用的是TFileStream类。使用TFileStream类来存取文件,首先要建立一个实例。声明如下:
    constructor Create(const Filename:string;Mode:Word);
    Filename为文件名(包括路径)
    Mode为打开文件的方式,它包括文件的打开模式和共享模式,其可能的取值和意义如下:
    打开模式:
    fmCreate :用指定的文件名建立文件,如果文件已经存在则打开它。
    fmOpenRead :以只读方式打开指定文件
    fmOpenWrite :以只写方式打开指定文件
    fmOpenReadWrite:以写写方式打开指定文件
    共享模式:
    fmShareCompat :共享模式与FCBs兼容
    fmShareExclusive:不允许别的程序以任何方式打开该文件
    fmShareDenyWrite:不允许别的程序以写方式打开该文件
    fmShareDenyRead :不允许别的程序以读方式打开该文件
    fmShareDenyNone :别的程序可以以任何方式打开该文件


    (三)资源文件
    1)、创建资源文件
    首先创建一个.Rc的纯文本文件。
    格式: 资源标识符 关键字 资源文件名

    资源标识符:程序中调用资源时的特殊标号;
    关键字:标识资源文件类型;
    Wave: 资源文件是声音文件;
    RCDATA: JPEG文件;
    AVI: AVI动画;
    ICON: 图标文件;
    BITMAP: 位图文件;
    CURSOR: 光标文件;
    EXEFILE : EXE文件
    资源文件名:资源文件的在磁盘上存储的文件全名

    例如:
    myzjy exefile zjy.exe

    2)、编译资源文件
    在DELPHI的安装目录的\Bin下,使用BRCC32.exe编译资源文件.RC。当然,也可以将BRCC32单独拷贝到程序文档目录使用。
    例如:
    Brcc32 wnhoo_reg.Rc

    3)、资源文件引用

    implementation

    {$R *.dfm}
    {$R wnhoo_reg.Res}

    4)、调用资源文件
    (1)存取资源文件中的位图(Bitmap)
    Image.Picture.Bitmap.Handle :=LoadBitmap(hInstance,'资源标识符');
    注:如果位图没有装载成功,程序仍旧执行,但是Image将不再显示图片。你可以根据LoadBitmap函数的返回值判断是否装载成功,如果装载成功返回值是非0,如果装载失败返回值是0。

    另外一个存取显示位图的方法如下
    Image.Picture.Bitmap.LoadFromResourceName(hInstance,'资源标识符');

    (2)存取资源文件中的光标
    Screen.Cursors[]是一个光标数组,使用光标文件我们可以将定制的光标加入到这个属性中。因为默认的光标在数组中索引值是0,所以除非想取代默认光标,最好将定制的光标索引值设为1。
    Screen.Cursors[1] :=LoadCursor(hInstance,'资源标识符');
    Image.Cursor :=1;

    (3)存取资源文件中的图标
    将图标放在资源文件中,可以实现动态改变应用程序图标。
    Application.Icon.Handle := LoadIcon(hInstance,'资源标识符');

    (4)存取资源文件中的AVI
    Animate.ResName :='MyAvi' ; //资源标识符号
    Animate.Active :=True ;

    (5)存取资源文件中的JPEG
    把jpeg单元加入到uses单元中。
    var
    Fjpg : TJpegImage ;
    FStream :TResourceStream ;
    begin
    Fjpg :=TJpegImage.Create ;
    //TresourceStream使用
    FStream := TResourceStream.Create (Hinstance,'资源标识符',资源类型) ;
    FJpg.LoadFromStream (FStream) ;
    Image.Picture.Bitmap.Assign (FJpg);

    (6)存取资源文件中的Wave
    把MMSystem加入uses单元中
    PlaySound(pchar('mywav'),Hinstance,Snd_ASync or Snd_Memory or snd_Resource) ;

    (四)INI文件操作
    (1) INI文件的结构:
    ;这是关于INI文件的注释部分
    [节点]
    关键字=值
    ...
    INI文件允许有多个节点,每个节点又允许有多个关键字, “=”后面是该关键字的值(类型有三种:字符串、整型数值和布尔值。其中字符串存贮在INI文件中时没有引号,布尔真值用1表示,布尔假值用0表示)。注释以分号“;”开头。

    (2) INI文件的操作
    1、 在Interface的Uses节增加IniFiles;
    2、 在Var变量定义部分增加一行:inifile:Tinifile;然后,就可以对变量myinifile进行创建、打开、读取、写入等操作了。
    3、 打开INI文件:inifile:=Tinifile.create('tmp.ini');
    4、 读取关键字的值:
    a:=inifile.Readstring('节点','关键字',缺省值);// string类型
    b:=inifile.Readinteger('节点','关键字',缺省值);// integer类型
    c:=inifile.Readbool('节点','关键字',缺省值);// boolean类型
    其中[缺省值]为该INI文件不存在该关键字时返回的缺省值。
    5、 写入INI文件:
    inifile.writestring('节点','关键字',变量或字符串值);
    inifile.writeinteger('节点','关键字',变量或整型值);
    inifile.writebool('节点','关键字',变量或True或False);
    当这个INI文件的节点不存在时,上面的语句还会自动创建该INI文件。
    6、 删除关键字:
    inifile.DeleteKey('节点','关键字');//关键字删除
    inifile.EraseSection('节点');// 节点删除
    7、 节点操作:
    inifile.readsection('节点',TStrings变量);//可将指定小节中的所有关键字名读取至一个字符串列表变量中;
    inifile.readsections(TStrings变量);//可将INI文件中所有小节名读取至一个字符串列表变量中去。
    inifile.readsectionvalues('节点',TStrings变量);//可将INI文件中指定小节的所有行(包括关键字、=、值)读取至一个字符串列表变量中去。
    8、 释放:inifile.distory;或inifile.free;

    (五)文件关联
    uses
    registry, shlobj;
    //实现关联注册
    procedure Tmyzip.regzzz;
    var
    reg: TRegistry;
    begin
    reg := TRegistry.Create;
    reg.RootKey := HKEY_CLASSES_ROOT;
    reg.OpenKey('.zzz', true);
    reg.WriteString('', 'myzip');
    reg.CloseKey;
    reg.OpenKey('myzip\shell\open\command', true);
    //用于打开.zzz文件的可执行程序
    reg.WriteString('', '"' + application.ExeName + '" "%1"');
    reg.CloseKey;
    reg.OpenKey('myzip\DefaultIcon',true);
    //取当前可执行程序的图标为.zzz文件的图标
    reg.WriteString('',''+application.ExeName+',0');
    reg.Free;
    //立即刷新
    SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);

    end;

    2.3、加密压缩的实现
    1、 生成INI临时加密文件
    用于加密的INI的临时文件格式:
    [FILE1]//节点,在软件中使用FILE1..N可以实现多文件加密
    FILENAME=压缩文件名
    PASSWORD=解压密码
    FILESIZE=文件大小
    FILEDATE=创建日期
    ISJM=解压是否需要密码
    如果是实现多文件、文件夹的信息存储,可以将密码关键字存在一个总的节点下。本文中仅是实现对单个文件的加密,所以只要上述格式就可以了。
    2、 将数据文件与用于加密的INI文件的合并,这可以采用文件流的形式实现。
    加密后文件结构图:
    图(1)

    图(2)


    上面两种形式,可以根据实际采用。本文采用图(1)的结构。
    3、 对于加密后的数据,采用ZLIB技术实现压缩存储,生成新压缩形式的文件。

    2.4、文件关联的实现 见2.2 (五)

    2.5、自解压的实现
    1. 建立一个专门用来自解压的可执行程序文件
    2. 将1中建立的文件,生成资源文件
    3. 将资源文件放到本文中这个压缩工具的程序中一起编译。
    4. 通过将资源文件与压缩文件的合并,生成自解压文件。
    自解压文件结构图:


    5.自解压实现:通过将自身文件中的加密压缩数据的分解,然后对分解的加密压缩数据再一次解压并分解出真正的数据文件。

    2.6 系统程序设计


    这是关于这个软件实现的核心部分全部代码,在这里详细讲述这个软件所有的技术细节。
    // wnhoo_zzz.pas

    unit wnhoo_zzz;
    interface

    uses
    Windows,Forms,SysUtils,Classes,zlib,Registry,INIFILES, Dialogs, shlobj;
    type
    pass=string[20];
    type
    Tmyzip = class

    private
    { private declarations here}
    protected
    { protected declarations here }
    public
    procedure regzzz;
    procedure ys_file(infileName, outfileName: string;password:pass;isjm:boolean;ysbz:integer);
    function jy_file(infileName: string;password:pass=''):boolean;
    procedure zjywj(var filename:string);
    constructor Create;
    destructor Destroy; override;
    { public declarations here }
    published
    { published declarations here }
    end;

    implementation

    constructor Tmyzip.Create;
    begin
    inherited Create; // 初始化继承下来的部分
    end;

    //#####################################################
    //原文件加密
    procedure jm_File(vfile:string;var Target:TMemoryStream;password:pass;isjm:boolean);
    {
    vfile:加密文件
    target:加密后输出目标流 》》》
    password:密码
    isjm:是否加密
    -------------------------------------------------------------
    加密后文件SIZE=原文件SIZE+[INI加密压缩信息文件]的SIZE+存储[INI加密压缩信息文件]的大小数据类型的SIZE
    ---------------------------------------------------------------
    }
    var

    tmpstream,inistream:TFileStream;
    FileSize:integer;
    inifile:TINIFILE;
    filename:string;
    begin
    //打开需要 [加密压缩文件]
    tmpstream:=TFileStream.Create(vFile,fmOpenread or fmShareExclusive);
    try
    //向 [临时加密压缩文件流] 尾部写入 [原文件流]
    Target.Seek(0,soFromEnd);
    Target.CopyFrom(tmpstream,0);
    //取得文件路径 ,生成 [INI加密压缩信息文件]
    filename:=ExtractFilePath(paramstr(0))+'tmp.in_';
    inifile:=TInifile.Create(filename);
    inifile.WriteString('file1','filename',ExtractFileName(vFile));
    inifile.WriteString('file1','password',password);
    inifile.WriteInteger('file1','filesize',Target.Size);
    inifile.WriteDateTime('file1','fileDate',now());
    inifile.WriteBool('file1','isjm',isjm);
    inifile.Free ;
    //读入 [INI加密压缩信息文件流]
    inistream:=TFileStream.Create(filename,fmOpenread or fmShareExclusive);
    try
    //继续在 [临时加密压缩文件流] 尾部加入 [INI加密压缩信息文件]
    inistream.Position :=0;
    Target.Seek(0,sofromend);
    Target.CopyFrom(inistream,0);
    //计算当前 [INI加密压缩信息文件] 的大小
    FileSize:=inistream.Size ;
    //继续在 [临时加密文件尾部] 加入 [INI加密压缩信息文件] 的SIZE信息
    Target.WriteBuffer(FileSize,sizeof(FileSize));
    finally
    inistream.Free ;
    deletefile(filename);
    end;
    finally
    tmpstream.Free;
    end;


    end;

    //**************************************************************

    //流压缩
    procedure ys_stream(instream, outStream: TStream;ysbz:integer);
    {
    instream: 待压缩的已加密文件流
    outStream 压缩后输出文件流
    ysbz:压缩标准
    }
    var
    ys: TCompressionStream;
    begin
    //流指针指向头部
    inStream.Position := 0;
    //压缩标准的选择
    case ysbz of
    1: ys := TCompressionStream.Create(clnone,OutStream);//不压缩
    2: ys := TCompressionStream.Create(clFastest,OutStream);//快速压缩
    3: ys := TCompressionStream.Create(cldefault,OutStream);//标准压缩
    4: ys := TCompressionStream.Create(clmax,OutStream); //最大压缩
    else

    ys := TCompressionStream.Create(clFastest,OutStream);
    end;

    try
    //压缩流
    ys.CopyFrom(inStream, 0);
    finally
    ys.Free;
    end;
    end;

    //*****************************************************************


    //流解压
    procedure jy_Stream(instream, outStream: TStream);
    {
    instream :原压缩流文件
    outStream:解压后流文件
    }
    var
    jyl: TDeCompressionStream;
    buf: array[1..512] of byte;
    sjread: integer;
    begin
    inStream.Position := 0;
    jyl := TDeCompressionStream.Create(inStream);
    try
    repeat
    //读入实际大小
    sjRead := jyl.Read(buf, sizeof(buf));
    if sjread > 0 then
    OutStream.Write(buf, sjRead);
    until (sjRead = 0);
    finally
    jyl.Free;
    end;
    end;


    //**************************************************************

    //实现关联注册
    procedure Tmyzip.regzzz;
    var
    reg: TRegistry;
    begin
    reg := TRegistry.Create;
    reg.RootKey := HKEY_CLASSES_ROOT;
    reg.OpenKey('.zzz', true);
    reg.WriteString('', 'myzip');
    reg.CloseKey;
    reg.OpenKey('myzip\shell\open\command', true);
    //用于打开.zzz文件的可执行程序
    reg.WriteString('', '"' + application.ExeName + '" "%1"');
    reg.CloseKey;
    reg.OpenKey('myzip\DefaultIcon',true);
    //取当前可执行程序的图标为.zzz文件的图标
    reg.WriteString('',''+application.ExeName+',0');
    reg.Free;
    //立即刷新
    SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);

    end;

    //压缩文件
    procedure Tmyzip.ys_file(infileName, outfileName: string;password:pass;isjm:boolean;ysbz:integer);
    {
    infileName://需要压缩加密的文件
    outfileName://压缩加密后产生的文件
    password://解压密码
    ysbz://压缩标准
    }
    var
    instream:TMemoryStream; //文件加密后的临时流
    outStream: TFileStream; //压缩输出文件流

    begin
    //创建 [文件加密后的临时流]
    instream:=TMemoryStream.Create;
    //文件加密
    jm_file(infileName,instream,password,isjm);
    //创建压缩输出文件流
    outStream := TFileStream.create(outFIleName, fmCreate);
    try
    //[文件加密后的临时流] 压缩
    ys_stream(instream,OutStream,ysbz);
    finally
    OutStream.free;
    instream.Free ;
    end;
    end;

    //解压文件
    function Tmyzip.jy_file(infileName: string;password:pass=''):boolean;
    var
    inStream,inistream,filestream_ok: TFileStream;
    {
    instream://解压文件名称
    inistream://INI临时文件流
    filestream_ok://解压OK的文件
    }
    outStream:tmemorystream; //临时内存流
    inifile:TINIFILE; //临时INI文件
    FileSize:integer; //密码文件的SIZE
    resultvalue:boolean;//返回值

    begin

    try
    inStream := TFileStream.create(inFIleName, fmOpenRead);

    try
    outStream := tmemorystream.create;
    try
    jy_stream(insTream,OutStream);
    //生成临时INI文件
    inistream:=TFileStream.create(ExtractFilePath(paramstr(0))+'tmp.in_', fmCreate);
    try
    //指向存储解码信息的INTEGER型变量位置
    OutStream.Seek(-sizeof(FileSize),sofromend);
    //读入变量信息
    OutStream.ReadBuffer(FileSize,sizeof(FileSize));
    //指向解码信息位置
    OutStream.Seek(-(FileSize+sizeof(FileSize)),sofromend);
    //将解码信息读入INI流中
    inistream.CopyFrom(OutStream,FileSize);
    //释放INI文件流
    inistream.Free ;
    //读入INI文件信息
    inifile:=TINIFILE.Create(ExtractFilePath(paramstr(0))+'tmp.in_');
    resultvalue:=inifile.ReadBool('file1','isjm',false);
    if resultvalue then
    begin
    if inifile.ReadString ('file1','password','')=trim(password) then
    resultvalue:=true
    else
    resultvalue:=false;
    end
    else
    resultvalue:=true;

    if resultvalue then
    begin

    filestream_ok:=TFileStream.create(ExtractFilePath(paramstr(1))+inifile.ReadString('file1','filename','wnhoo.zzz'),fmCreate);
    try
    OutStream.Position :=0;
    filestream_ok.CopyFrom(OutStream,inifile.ReadInteger('file1','filesize',0));
    finally
    filestream_ok.Free ;
    end;

    end;


    inifile.Free;
    finally
    //删除临时INI文件
    deletefile(ExtractFilePath(paramstr(0))+'tmp.in_');
    end;
    //
    finally
    OutStream.free;
    end;
    finally
    inStream.free;
    end;
    except
    resultvalue:=false ;

    end;
    result:=resultvalue;
    end;



    //自解压创建
    procedure tmyzip.zjywj(var filename:string);
    var
    myRes: TResourceStream;//临时存放自解压EXE文件
    myfile:tfilestream;//原文件流
    xfilename:string;//临时文件名称
    file_ok:tmemorystream; //生成文件的内存流
    filesize:integer; //原文件大小
    begin
    if FileExists(filename) then
    begin
    //创建内存流
    file_ok:=tmemorystream.Create ;
    //释放资源文件-- 自解压EXE文件
    myRes := TResourceStream.Create(Hinstance, 'myzjy', Pchar('exefile'));
    //将原文件读入内存
    myfile:=tfilestream.Create(filename,fmOpenRead);
    try

    myres.Position:=0;
    file_ok.CopyFrom(myres,0);
    file_ok.Seek(0,sofromend);
    myfile.Position:=0;
    file_ok.CopyFrom(myfile,0);
    file_ok.Seek(0,sofromend);
    filesize:=myfile.Size;
    file_ok.WriteBuffer(filesize,sizeof(filesize));
    file_ok.Position:=0;
    xfilename:=ChangeFileExt(filename,'.exe') ;
    file_ok.SaveToFile(xfilename);

    finally
    myfile.Free ;
    myres.Free ;
    file_ok.Free ;

    end;
    DeleteFile(filename);
    filename:=xfilename;

    end;
    end;

    //#####################################################

    destructor Tmyzip.Destroy;
    begin

    inherited Destroy;
    end;
    end.

    3 、结束语
    Delphi 的全新可视化编程环境,为我们提供了一种方便、快捷的Windows应用程序开发工具。对于程序开发人员来讲,使用Delphi开发应用软件,无疑会大大 地提高编程效率。在delphi中可以很方便的利用流实现文件处理、动态内存处理、网络数据处理等多种数据形式,写起程序也会大大提高效率的。

    参考文献:
    1、DELPHI系统帮助
    2、冯志强. Delphi 中压缩流和解压流的应用
    3、陈经韬.谈Delphi编程中“流”

    遍历所有硬盘的所有目录

    关键词:遍历 文件夹 目录
    //一个遍历所有硬盘的所有目录的实例源码:

    unit Unit1;

    interface

    uses
    Windows, Messages, FileCtrl,SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
    ComCtrls, StdCtrls, ImgList, ExtCtrls;

    type
    TForm1 = class(TForm)
    TreeView: TTreeView;
    Button3: TButton;
    procedure Button3Click(Sender: TObject);
    private
    { Private declarations }
    public
    procedure CreateDirectoryTree(RootDir, RootCaption: string);
    end;

    var
    Form1: TForm1;

    implementation

    {$R *.DFM}
    procedure TForm1.CreateDirectoryTree(RootDir, RootCaption: string);
    procedure AddSubDirToTree(RootNode: TTreeNode);
    var
    SearchRec: TSearchRec;
    Path: string;
    Found: integer;
    begin
    Path := PChar(RootNode.Data) + '\*.*';
    Found := FindFirst(Path, faAnyFile, SearchRec);
    while Found = 0 do
    begin
    if (SearchRec.Attr = faDirectory) and (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
    AddSubDirToTree(TreeView.Items.AddChildObject(RootNode, SearchRec.Name,
    PChar(PChar(RootNode.Data) + '\' + SearchRec.Name)));
    Found := FindNext(SearchRec);
    end;
    FindClose(SearchRec);
    end;
    begin
    //TreeView.Items.Clear;
    AddSubDirToTree(TreeView.Items.AddObject(nil, RootCaption, PChar(RootDir)));
    end;

    procedure TForm1.Button3Click(Sender: TObject);
    var
    i:integer;
    abc:Tstrings;
    s:string;
    begin
    abc:=TStringlist.Create;
    for i:=0 to 23 do begin
    s := Chr(65+i)+':\';
    // if GetDriveType(PChar(s))= DRIVE_cdrom then
    if directoryexists(s) then
    begin
    s:=copy(s,0,2) ;
    abc.Add(s);
    end;
    end;
    for i:=0 to abc.Count-1 do
    BEGIN
    S:=abc.strings[i];
    CreateDirectoryTree(S, '['+s+'\]');
    END
    end;

    end.

    文件或目录转换成TreeView


    关键词:treeview
    下面的这个函数就可以了:
    procedure DirToTreeView(Tree: TTreeView; Directory: string; Root: TTreeNode; IncludeFiles:

    Boolean);

    var

    SearchRec : TSearchRec;

    ItemTemp : TTreeNode;

    begin

    with Tree.Items do

    try

    BeginUpdate;

    if Directory[Length(Directory)] <> ' then Directory := Directory + ';

    if FindFirst(Directory + '*.*', faDirectory, SearchRec) = 0 then

    begin

    repeat

    if (SearchRec.Attr and faDirectory = faDirectory) and (SearchRec.Name[1] <> '.') then

    begin

    if (SearchRec.Attr and faDirectory > 0) then

    Root := AddChild(Root, SearchRec.Name);

    ItemTemp := Root.Parent;

    DirToTreeView(Tree, Directory + SearchRec.Name, Root, IncludeFiles);

    Root := ItemTemp;

    end

    else if IncludeFiles then

    if SearchRec.Name[1] <> '.' then

    AddChild(Root, SearchRec.Name);

    until FindNext(SearchRec) <> 0;

    FindClose(SearchRec);

    end;

    finally

    EndUpdate;

    end;

    end;


    如何判断一目录是否共享

    关键词:判断 共享目录 共享文件夹
    Shell编程---如何判断一目录是否共享?

    下面函数要额外引用 ShlObj, ComObj, ActiveX 单元。

    function TForm1.IfFolderShared(FullFolderPath: string): Boolean;

    //将TStrRet类型转换为字符串

    function StrRetToString(PIDL: PItemIDList; StrRet: TStrRet; Flag:string=''): string;

    var

    P: PChar;

    begin

    case StrRet.uType of

    STRRET_CSTR:

    SetString(Result, StrRet.cStr, lStrLen(StrRet.cStr));

    STRRET_OFFSET:

    begin

    P := @PIDL.mkid.abID[StrRet.uOffset - SizeOf(PIDL.mkid.cb)];

    SetString(Result, P, PIDL.mkid.cb - StrRet.uOffset);

    end;

    STRRET_WSTR:

    if Assigned(StrRet.pOleStr) then

    Result := StrRet.pOleStr

    else

    Result := '';

    end;

    { This is a hack bug fix to get around Windows Shell Controls returning

    spurious "?"s in date/time detail fields }

    if (Length(Result) > 1) and (Result[1] = '?') and (Result[2] in ['0'..'9']) then

    Result := StringReplace(Result,'?','',[rfReplaceAll]);

    end;

    //返回Desktop的IShellFolder接口

    function DesktopShellFolder: IShellFolder;

    begin

    OleCheck(SHGetDesktopFolder(Result));

    end;

    //返回IDList去掉第一个ItemID后的IDList

    function NextPIDL(IDList: PItemIDList): PItemIDList;

    begin

    Result := IDList;

    Inc(PChar(Result), IDList^.mkid.cb);

    end;

    //返回IDList的长度

    function GetPIDLSize(IDList: PItemIDList): Integer;

    begin

    Result := 0;

    if Assigned(IDList) then

    begin

    Result := SizeOf(IDList^.mkid.cb);

    while IDList^.mkid.cb <> 0 do

    begin

    Result := Result + IDList^.mkid.cb;

    IDList := NextPIDL(IDList);

    end;

    end;

    end;

    //取得IDList中ItemID的个数

    function GetItemCount(IDList: PItemIDList): Integer;

    begin

    Result := 0;

    while IDList^.mkid.cb <> 0 do

    begin

    Inc(Result);

    IDList := NextPIDL(IDList);

    end;

    end;

    //创建一ItemIDList对象

    function CreatePIDL(Size: Integer): PItemIDList;

    var

    Malloc: IMalloc;

    begin

    OleCheck(SHGetMalloc(Malloc));

    Result := Malloc.Alloc(Size);

    if Assigned(Result) then

    FillChar(Result^, Size, 0);

    end;

    //返回IDList的一个内存拷贝

    function CopyPIDL(IDList: PItemIDList): PItemIDList;

    var

    Size: Integer;

    begin

    Size := GetPIDLSize(IDList);

    Result := CreatePIDL(Size);

    if Assigned(Result) then

    CopyMemory(Result, IDList, Size);

    end;

    //返回AbsoluteID最后一个ItemID,即此对象相对于父对象的ItemID

    function RelativeFromAbsolute(AbsoluteID: PItemIDList): PItemIDList;

    begin

    Result := AbsoluteID;

    while GetItemCount(Result) > 1 do

    Result := NextPIDL(Result);

    Result := CopyPIDL(Result);

    end;

    //将IDList的最后一个ItemID去掉,即得到IDList的父对象的ItemID

    procedure StripLastID(IDList: PItemIDList);

    var

    MarkerID: PItemIDList;

    begin

    MarkerID := IDList;

    if Assigned(IDList) then

    begin

    while IDList.mkid.cb <> 0 do

    begin

    MarkerID := IDList;

    IDList := NextPIDL(IDList);

    end;

    MarkerID.mkid.cb := 0;

    end;

    end;

    //判断返回值Flag中是否包含属性Element

    function IsElement(Element, Flag: Integer): Boolean;

    begin

    Result := Element and Flag <> 0;

    end;

    var

    P: Pointer;

    NumChars, Flags: LongWord;

    ID, NewPIDL, ParentPIDL: PItemIDList;

    ParentShellFolder: IShellFolder;

    begin

    Result := false;

    NumChars := Length(FullFolderPath);

    P := StringToOleStr(FullFolderPath);

    //取出该目录的绝对ItemIDList

    OleCheck(DesktopShellFolder.ParseDisplayName(0, nil, P, NumChars, NewPIDL, Flags));

    if NewPIDL <> nil then

    begin

    ParentPIDL := CopyPIDL(NewPIDL);

    StripLastID(ParentPIDL); //得到该目录上一级目录的ItemIDList

    ID := RelativeFromAbsolute(NewPIDL); //得到该目录相对于上一级目录的ItemIDList

    //取得该目录上一级目录的IShellFolder接口

    OleCheck(DesktopShellFolder.BindToObject(ParentPIDL, nil, IID_IShellFolder,

    Pointer(ParentShellFolder)));

    if ParentShellFolder <> nil then

    begin

    Flags := SFGAO_SHARE;

    //取得该目录的属性

    OleCheck(ParentShellFolder.GetAttributesOf(1, ID, Flags));

    if IsElement(SFGAO_SHARE, Flags) then Result := true;

    end;

    end;

    end;

    此函数的用法:

    //传进的参数为一目录的全路经

    if IfFolderShared('C:Documents') then showmessage('shared')

    else showmessage('not shared');

    另 外,有一函数 SHBindToParent 可以直接取得此目录的上一级目录的IShellFolder接口和此目录相对于上一级目录的ItemIDList,这样一来就省去了上面多个对 ItemIDList进行操作的函数(这些函数从delphi6的TShellTreeView所在的单元拷贝而来),但是此函数为新加入的API,只在 win2000、winxp和winme下可以使用(这么有用的函数微软怎么就没早点想出来呢 ?)


    delphi 搜索指定目录下的文件 源码

    修改FindFirst(Directory+'\*.*', $0000003F, SearchRec) = 0 中的

    "\*.*"部分即可找到你所需要的特定文件

    例如:\*.txt     表示搜索搜索指定目录下的txt文件



    通过API函数得到操作系统类型

    unit Unit1;

    interface

    uses
       Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
       Dialogs, StdCtrls;

    type
       TForm1 = class(TForm)
         Label1: TLabel;
         Edit1: TEdit;
         Memo1: TMemo;
         Label2: TLabel;
         Button1: TButton;
         procedure FormCreate(Sender: TObject);
         procedure Button1Click(Sender: TObject);
       private
         { Private declarations }
       public
         { Public declarations }

         Function FindAllFileInADirectory(Directory:string;RetList : TStringList): TStringList;
       end;

    var
       Form1: TForm1;

    implementation

    {$R *.dfm}

    procedure TForm1.FormCreate(Sender: TObject);
    begin
    memo1.Text:='';
    end;


    Function TForm1.FindAllFileInADirectory(Directory:string;RetList:TStringList): TStringList;
    var
    SearchRec: TSearchRec;
    begin
       if FindFirst(Directory+'\*.*', $0000003F, SearchRec) = 0 then
       begin
         repeat
         RetList.Add(Directory +'\'+ SearchRec.Name);
         until (FindNext(SearchRec) <> 0);
       end;
       FindClose(SearchRec);
    end;


    procedure TForm1.Button1Click(Sender: TObject);
    var
    TempList:TStringList;
    begin
    TempList:=TStringList.Create;
    TempList:=FindAllFileInADirectory(Edit1.text,TempList);
    memo1.Lines.Clear;
    memo1.Lines.Assign(TempList);
    TempList.clear;
    end;

    end.      //end unit

    unit Unit1;

    interface

    uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
    StdCtrls, CommDlg;

    type
    TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    private
    { Private declarations }
    public
    { Public declarations }
    end;
    type
    TOSVersion = (osUnknown, os95, os95OSR2, os98, os98SE, osNT3, osNT4, os2K, osME, osXP);

    var
    Form1: TForm1;

    implementation

    {$R *.DFM}

    function GetOS :TOSVersion;
    var
    OS :TOSVersionInfo;
    begin
       ZeroMemory(@OS,SizeOf(OS));
       OS.dwOSVersionInfoSize:=SizeOf(OS);
       GetVersionEx(OS);
       Result:=osUnknown;
      if OS.dwPlatformId=VER_PLATFORM_WIN32_NT then begin
        case OS.dwMajorVersion of
         3: Result:=osNT3;
         4: Result:=osNT4;
         5: Result:=os2K;
      end;
      if (OS.dwMajorVersion=5) and (OS.dwMinorVersion=1) then
       Result:=osXP;
    end else begin
      if (OS.dwMajorVersion=4) and (OS.dwMinorVersion=0) then begin
         Result:=os95;
        if (Trim(OS.szCSDVersion)='B') then
         Result:=os95OSR2;
      end else
      if (OS.dwMajorVersion=4) and (OS.dwMinorVersion=10) then begin
         Result:=os98;
        if (Trim(OS.szCSDVersion)='A') then
         Result:=os98SE;
      end else
      if (OS.dwMajorVersion=4) and (OS.dwMinorVersion=90) then
       Result:=osME;
    end;
    end;
    procedure TForm1.Button1Click(Sender: TObject);
    var
    os:TosVersion;
    osVersion:string;
    begin
       os:=Getos;
      case OS of
       os95, os95OSR2: OSVersion:='Windows 95';
       os98: OSVersion:='Windows 98';
       os98SE: OSVersion:='Windows 98 第二版';
       osME: OSVersion:='Windows Millenium Edition';
       osNT3, osNT4: OSVersion:='Windows NT';
       os2K: OSVersion:='Windows 2000';
       osXP: OSVersion:='Windows XP';
    end;
    showmessage(osversion);
    end;

    end.
1045/6<123456>
Open Toolbar