socketref,再见!高德

https://github.com/adoggie

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  246 Posts :: 4 Stories :: 312 Comments :: 0 Trackbacks

常用链接

留言簿(54)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

#

/sbin/service crond start     //启动服务
/sbin/service crond stop     //关闭服务
/sbin/service crond restart //重启服务
/sbin/service crond reload     //重新载入配置

在/etc/rc.d/rc.local这个脚本的末尾加上:
/sbin/service crond start


1.直接用crontab命令编辑
cron服务提供crontab命令来设定cron服务的,以下是这个命令的一些参数与说明:
crontab -u //设定某个用户的cron服务,一般root用户在执行这个命令的时候需要此参数

crontab -l //列出某个用户cron服务的详细内容

crontab -r //删除没个用户的cron服务

crontab -e //编辑某个用户的cron服务

比如说root查看自己的cron设置:crontab -u root -l

再例如,root想删除fred的cron设置:crontab -u fred -r

在编辑cron服务时,编辑的内容有一些格式和约定,输入:crontab -u root -e

进入vi编辑模式,编辑的内容一定要符合下面的格式:*/1 * * * * ls >> /tmp/ls.txt

时间的设定我们有一定的约定,前面五个*号代表五个数字,数字的取值范围和含义如下:
分钟 (0-59)
小時 (0-23)
日期 (1-31)
月份 (1-12)
星期 (0-6)//0代表星期天

除了数字还有几个个特殊的符号就是"*"、"/"和"-"、",",*代表所有的取值范围内的数字,"/"代表每的意思,"*/5"表示每5个单位,"-"代表从某个数字到某个数字,","分开几个离散的数字。以下举几个例子说明问题:

每天早上6点

0 6 * * * echo "Good morning." >> /tmp/test.txt //注意单纯echo,从屏幕上看不到任何输出,因为cron把任何输出都email到root的信箱了。

每两个小时

0 */2 * * * echo "Have a break now." >> /tmp/test.txt

晚上11点到早上8点之间每两个小时,早上八点

0 23-7/2,8 * * * echo "Have a good dream:)" >> /tmp/test.txt

每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点

0 11 4 * 1-3 command line

1月1日早上4点

0 4 1 1 * command line

每次编辑完某个用户的cron设置后,cron自动在/var/spool/cron下生成一个与此用户同名的文件,此用户的cron信息都记录在这个文件中,这个文件是不可以直接编辑的,只可以用crontab -e 来编辑。cron启动后每过一份钟读一次这个文件,检查是否要执行里面的命令。因此此文件修改后不需要重新启动cron服务。

2.编辑/etc/crontab 文件配置cron

cron 服务每分钟不仅要读一次/var/spool/cron内的所有文件,还需要读一次/etc/crontab,因此我们配置这个文件也能运用cron服务做一些事情。用crontab配置是针对某个用户的,而编辑/etc/crontab是针对系统的任务。此文件的文件格式是:

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root //如果出现错误,或者有数据输出,数据作为邮件发给这个帐号
HOME=/ //使用者运行的路径,这里是根目录
# run-parts
01 * * * * root run-parts /etc/cron.hourly //每小时执行/etc/cron.hourly内的脚本
02 4 * * * root run-parts /etc/cron.daily //每天执行/etc/cron.daily内的脚本
22 4 * * 0 root run-parts /etc/cron.weekly //每星期执行/etc/cron.weekly内的脚本
42 4 1 * * root run-parts /etc/cron.monthly //每月去执行/etc/cron.monthly内的脚本


posted @ 2008-07-04 01:25 放屁阿狗 阅读(387) | 评论 (0)编辑 收藏

{
        TCHAR pText[] = _T("\\Windows\\0\\NandFlashPartition\\MediaLib\0\\Storage Card\\MediaLib\0\0");
        CString strText(pText,sizeof(pText));
        SaveMultString(HKEY_LOCAL_MACHINE,_T("Loader"),_T("SystemPath"),strText,sizeof(pText));

        DeleteKey(HKEY_LOCAL_MACHINE,_T("SOFTWARE\\sample\\PLAY"));
    }
posted @ 2008-07-04 01:24 放屁阿狗 阅读(1532) | 评论 (0)编辑 收藏

cpio(copy in/out)

功能说明:备份文件。

语  法:cpio [-0aABckLovV][-C <输入/输出大小>][-F <备份档>][-H <备份格式>][-O <备份档>][--block-size= <区块大小>][--force-local][--help][--quiet][--version] 或 cpio [- bBcdfikmnrsStuvV][-C <输入/输出大小>][-E <范本文件>][-F <备份档>][- H <备份格式>][-I <备份档>][-M <回传信息>][-R <拥有者><: /.><所属群组>][--block-size=<区块大小>][--force-local][--help][-- no-absolute-filenames][--no-preserve-owner][--only-verify-crc][--quiet][- -sparse][--version][范本样式...] 或 cpio [-0adkiLmpuvV][-R <拥有者><: /.><所属群组>][--help][--no-preserve-owner][--quiet][--sparse][-- version][目的目]

补充说明:cpio是用来建立,还原备份档的工具程序,它可以加入,解开cpio或tra备份档内的文件。

参  数:
 -0或--null  接受新增列控制字符,通常配合find指令的"-print0"参数使用。
 -a或--reset-access-time  重新设置文件的存取时间。
 -A或--append  附加到已存在的备份档中,且这个备份档必须存放在磁盘上,而不能放置于磁带机里。
 -b或--swap  此参数的效果和同时指定"-sS"参数相同。
 -B  将输入/输出的区块大小改成5210 Bytes。
 -c  使用旧ASCII备份格式。
 -C<区块大小>或--io-size=<区块大小>  设置输入/输出的区块大小,单位是Byte。
 -d或--make-directories  如有需要cpio会自行建立目录。
 -E<范本文件>或--pattern-file=<范本文件>  指定范本文件,其内含有一个或多个范本样式,让cpio解开符合范本条件的文件,格式为每列一个范本样式。
 -f或--nonmatching  让cpio解开所有不符合范本条件的文件。
 -F<备份档>或--file=<备份档>  指定备份档的名称,用来取代标准输入或输出,也能借此通过网络使用另一台主机的保存设备存取备份档。
 -H<备份格式>  指定备份时欲使用的文件格式。
 -i或--extract  执行copy-in模式,还原备份档。
 -l<备份档>  指定备份档的名称,用来取代标准输入,也能借此通过网络使用另一台主机的保存设备读取备份档。
 -k  此参数将忽略不予处理,仅负责解决cpio不同版本间的兼容性问题。
 -l或--link  以硬连接的方式取代复制文件,可在copy-pass模式下运用。
 -L或--dereference  不建立符号连接,直接复制该连接所指向的原始文件。
 -m或preserve-modification-time  不去更换文件的更改时间。
 -M<回传信息>或--message=<回传信息>  设置更换保存媒体的信息。
 -n或--numeric-uid-gid  使用"-tv"参数列出备份档的内容时,若再加上参数"-n",则会以用户识别码和群组识别码替代拥有者和群组名称列出文件清单。
 -o或--create  执行copy-out模式,建立备份档。
 -O<备份档>  指定备份档的名称,用来取代标准输出,也能借此通过网络 使用另一台主机的保存设备存放备份档。
 -p或--pass-through  执行copy-pass模式,略过备份步骤,直接将文件复制到目的目录。
 -r或--rename  当有文件名称需要更动时,采用互动模式。
 -R<拥有者><:/.><所属群组>或
 ----owner<拥有者><:/.><所属群组>  在copy-in模式还原备份档,或copy-pass模式复制文件时,可指定这些备份,复制的文件的拥有者与所属群组。
 -s或--swap-bytes  交换每对字节的内容。
 -S或--swap-halfwords  交换每半个字节的内容。
 -t或--list  将输入的内容呈现出来。
 -u或--unconditional  置换所有文件,不论日期时间的新旧与否,皆不予询问而直接覆盖。
 -v或--verbose  详细显示指令的执行过程。
 -V或--dot  执行指令时,在每个文件的执行程序前面加上"."号
 --block-size=<区块大小>  设置输入/输出的区块大小,假如设置数值为5,则区块大小为2500,若设置成10,则区块大小为5120,依次类推。
 --force-local  强制将备份档存放在本地主机。
 --help  在线帮助。
 --no-absolute-filenames  使用相对路径建立文件名称。
 --no-preserve-owner  不保留文件的拥有者,谁解开了备份档,那些文件就归谁所有。
 -only-verify-crc  当备份档采用CRC备份格式时,可使用这项参数检查备份档内的每个文件是否正确无误。
 --quiet  不显示复制了多少区块。
 --sparse  倘若一个文件内含大量的连续0字节,则将此文件存成稀疏文件。
 --version  显示版本信息。
posted @ 2008-07-04 01:23 放屁阿狗 阅读(196) | 评论 (0)编辑 收藏

cvs server
===================
1. cvs -d /path/cvsroot init
2. groupadd cvs
3. chgrp -R cvs /path/cvsroot
4. chmod -R ug+rxw /path/cvsroot
5. edit /etc/groups, add cvs-user to cvs group ( cvs:::scott,may)
6. edit /etc/services,add following line:
     cvspserver  ... 2401
7. create /etc/xinetd.d/cvspserver, add following text:
    service cvspserver
    {
        disable = no
        flags           = REUSE
        protocal        = tcp
        socket_type     = stream
        wait            = no
        user            = root
        server          = /usr/bin/cvs
        server_args     = --allow-root=/home/cvsroot pserver
    }
8. service restart xinetd

cvs client setting
===================
1. vi .bash_profile,add CVSROOT varaible
    CVSROOT=:pserver:user@host#2401:/server/path/cvsroot
2. cvs login
3. cvs import -m="test"  project vendor release_version
4. cvs checkout project   






CVS环境初始化
============

环境设置:指定CVS库的路径CVSROOT
tcsh
setenv CVSROOT /path/to/cvsroot
bash
CVSROOT=/path/to/cvsroot ; export CVSROOT

后面还提到远程CVS服务器的设置:
CVSROOT=:ext:$USER@test.server.address#port:/path/to/cvsroot CVS_RSH=ssh; export CVSROOT CVS_RSH

初始化:CVS版本库的初始化。
cvs init

一个项目的首次导入
cvs import -m "write some comments here" project_name vendor_tag release_tag
执行后:会将所有源文件及目录导入到/path/to/cvsroot/project_name目录下
vender_tag: 开发商标记
release_tag: 版本发布标记

项目导出:将代码从CVS库里导出
cvs checkout project_name
cvs 将创建project_name目录,并将最新版本的源代码导出到相应目录中。这个checkout和Virvual SourceSafe中的check out不是一个概念,相对于Virvual SourceSafe的check out是cvs update, check in是cvs commit。

CVS的日常使用 
=============

注意:第一次导出以后,就不是通过cvs checkout来同步文件了,而是要进入刚才cvs checkout project_name导出的project_name目录下进行具体文件的版本同步(添加,修改,删除)操作。

将文件同步到最新的版本:
cvs update
不制定文件名,cvs将同步所有子目录下的文件,也可以制定某个文件名/目录进行同步
cvs update file_name
最好每天开始工作前或将自己的工作导入到CVS库里前都要做一次,并养成“先同步 后修改”的习惯,和Virvual SourceSafe不同,CVS里没有文件锁定的概念,所有的冲突是在commit之前解决,如果你修改过程中,有其他人修改并commit到了CVS库中,CVS会通知你文件冲突,并自动将冲突部分用
>>>>>>
content on cvs server
<<<<<<
content in your file
>>>>>>
标记出来,由你确认冲突内容的取舍。
版本冲突一般是在多个人修改一个文件造成的,但这种项目管理上的问题不应该指望由CVS来解决。

确认修改写入到CVS库里:
cvs commit -m "write some comments here" file_name

注意:CVS的很多动作都是通过cvs commit进行最后确认并修改的,最好每次只修改一个文件。在确认的前,还需要用户填写修改注释,以帮助其他开发人员了解修改的原因。如果不用写-m "comments"而直接确认`cvs commit file_name` 的话,cvs会自动调用系统缺省的文字编辑器(一般是vi)要求你写入注释。
注释的质量很重要:所以不仅必须要写,而且必须写一些比较有意义的内容:以方便其他开发人员能够很好的理解
不好的注释,很难让其他的开发人员快速的理解:比如: -m "bug fixed" 甚至 -m ""
好的注释,甚至可以用中文: -m "在用户注册过程中加入了Email地址校验"

修改某个版本注释:每次只确认一个文件到CVS库里是一个很好的习惯,但难免有时候忘了指定文件名,把多个文件以同样注释commit到CVS库里了,以下命令可以允许你修改某个文件某个版本的注释:
cvs admin -m 1.3:"write some comments here" file_name

添加文件
创建好新文件后,比如:touch new_file
cvs add new_file
注意:对于图片,Word文档等非纯文本的项目,需要使用cvs add -b选项,否则有可能出现文件被破坏的情况
比如:cvs add -kb new_file.gif
然后确认修改并注释
cvs ci -m "write some comments here"

删除文件:
将某个源文件物理删除后,比如:rm file_name
cvs rm file_name
然后确认修改并注释
cvs ci -m "write some comments here"
以上面前2步合并的方法为:
cvs rm -f file_name
cvs ci -m "why delete file"

注意:很多cvs命令都有缩写形式:commit=>ci; update=>up; checkout=>co; remove=>rm;


添加目录:
cvs add dir_name

查看修改历史:cvs log file_name
cvs history file_name

查看当前文件不同版本的区别
cvs diff -r1.3 -r1.5 file_name
查看当前文件(可能已经修改了)和库中相应文件的区别
cvs diff file_name
cvs的web界面提供了更方便的定位文件修改和比较版本区别的方法,具体安装设置请看后面的cvsweb使用

正确的通过CVS恢复旧版本的方法:
如果用cvs update -r1.2 file.name
这个命令是给file.name加一个STICK TAG: "1.2" ,虽然你的本意只是想将它恢复到1.2版本
正确的恢复版本的方法是:cvs update -p -r1.2 file_name >file_name
如果不小心已经加成STICK TAG的话:用cvs update -A 解决

移动文件:文件重命名
cvs里没有cvs move或cvs rename,因为这两个操作是先cvs remove old_file_name,然后cvs add new_file_name实现的。

删除,移动目录:
最方便的方法是让管理员直接移动,删除CVSROOT里相应目录(因为CVS一个项目下的子目录都是独立的,移动到$CVSROOT目录下都可以作为新的独立项目:好比一颗树,其实砍下任意一枝都能独立存活),对目录进行了修改后,要求其开发人员重新导出项目cvs checkout project_name 或者用cvs update -dP同步。

CVS Branch:项目多分支同步开发
=============================

确认版本里程碑:多个文件各自版本号不一样,项目到一定阶段,可以给所有文件统一指定一个阶段里程碑版本号,方便以后按照这个阶段里程碑版本号导出项目,同时也是项目的多个分支开发的基础。
cvs tag release_1_0

开始一个新的里程碑:
cvs commit -r 2 标记所有文件开始进入2.x的开发

注意:CVS里的revsion和软件包的发布版本可以没有直接的关系。但所有文件使用和发布版本一致的版本号比较有助于维护。

在开发项目的2.x版本的时候发现1.x有问题,但2.x又不敢用,则从先前标记的里程碑:release_1_0导出一个分支release_1_0_patch
cvs rtag -b -r release_1_0 release_1_0_patch proj_dir

一些人先在另外一个目录下导出release_1_0_patch这个分支:解决1.0中的紧急问题,
cvs checkout -r release_1_0_patch
而其他人员仍旧在项目的主干分支2.x上开发

在release_1_0_patch上修正错误后,标记一个1.0的错误修正版本号
cvs tag release_1_0_patch_1

如果2.0认为这些错误修改在2.0里也需要,也可以在2.0的开发目录下合并release_1_0_patch_1中的修改到当前代码中:
cvs update -j release_1_0_patch_1

CVS的远程认证:通过SSH远程访问CVS
================================

使用cvs本身的远程认证很麻烦,需要定义服务器和用户组,用户名,设置密码等,而且不安全,因此和系统本地帐号认证并通过SSH传输是比较好的办法,通过在客户机的/etc/profile里设置一下内容:
CVSROOT=:ext:$USER@test.server.address#port:/path/to/cvsroot CVS_RSH=ssh; export CVSROOT CVS_RSH
所有客户机所有本地用户都可以映射到CVS服务器相应同名帐号了。

如果CVS所在服务器的SSH端口不在缺省的22,或者和客户端与CVS服务器端SSH缺省端口不一致,有时候设置了:
:ext:$USER@test.server.address#port:/path/to/cvsroot

仍然不行,比如有以下错误信息:
ssh: test.server.address#port: Name or service not known
cvs [checkout aborted]: end of file from server (consult above messages if any)

解决的方法是做一个脚本指定端口转向(不能使用alias,会出找不到文件错误):
创建一个/usr/bin/ssh_cvs文件:
#!/usr/bin/sh
/path/to/ssh -p 34567 "$@"
然后:chmod +x /usr/bin/ssh_cvs
并CVS_RSH=ssh_cvs; export CVS_RSH

注意:port是指相应服务器SSH的端口,不是cvs pserver的端口

CVSWEB:提高程序员比较文件修改效率
================================

CVSWEB就是CVS的WEB界面,可以大大提高程序员定位修改的效率:
使用的样例可以看:http://www.freebsd.org/cgi/cvsweb.cgi

CVSWEB的下载:CVSWEB从最初的版本已经演化出很多功能界面更丰富的版本,这个是个人感觉觉得安装设置比较方便的:
http://www.spaghetti-code.de/software/linux/cvsweb/

下载解包:
tar zxf cvsweb.tgz
把配置文件cvsweb.conf放到安全的地方(比如和apache的配置放在同一个目录下),
修改:cvsweb.cgi让CGI找到配置文件:
$config = $ENV{'CVSWEB_CONFIG'} || '/path/to/apache/conf/cvsweb.conf';

转到/path/to/apache/conf下并修改cvsweb.conf:

   1. 修改CVSROOT路径设置:
      %CVSROOT = (
      'Development' => '/path/to/cvsroot', #<==修改指向本地的CVSROOT
      );
   2. 缺省不显示已经删除的文档:
      "hideattic" => "1",#<==缺省不显示已经删除的文档
   3. 在配置文件cvsweb.conf中还可以定制页头的描述信息,你可以修改$long_intro成你需要的文字

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>

CVS TAGS: who? when?
====================

将$Id$ 加在程序文件开头的注释里是一个很好的习惯,cvs能够自动解释更新其中的内容成:file_name version time user_name 的格式,比如:cvs_card.txt,v 1.1 2002/04/05 04:24:12 chedong Exp,可以这些信息了解文件的最后修改人和修改时间

几个常用的缺省文件:
default.php
<?php
/*
* Copyright (c) 2002 Company Name.
* $Header$
*/

?>

====================================
Default.java: 注意文件头一般注释用 /* 开始 JAVADOC注释用 /** 开始的区别
/*
* Copyright (c) 2002 Company Name.
* $Header$
*/

package com.netease;

import java.io;

/**
* comments here
*/
public class Default {
    /**
    *
    * @param
    * @return
    */
    public toString() {

    }
}

====================================
default.pl:
#!/usr/bin/perl -w
# Copyright (c) 2002 Company Name.
# $Header$

# file comments here

use strict;

CVS vs VSS 
===========

CVS没有文件锁定模式,VSS在check out同时,同时记录了文件被导出者锁定。

CVS是update commit, VSS是check out check in

在CVS中,标记自动更新功能缺省是打开的,这样也带来一个潜在的问题,就是不用-kb方式添加binary文件的话在cvs自动更新时可能会导致文件失效。

Virsual SourceSafe中这个功能称之为Keyword Explaination,缺省是关闭的,需要通过OPITION打开,并指定需要进行源文件关键词扫描的类型:*.txt,*.java,*.html...

对于Virsual SourceSafe和CVS都通用的TAG有:
$Header$
$Author$
$Date$
$Revision$

尽量使用通用的关键词保证代码在CVS和VSS都能方便的跟踪。

 
posted @ 2008-07-04 01:22 放屁阿狗 阅读(246) | 评论 (0)编辑 收藏

网络用户有时候会遇到需要下载一批文件的情况,有时甚至需要把整个网站下载下来或者制作网站的镜像。在Windows下的用户都比较熟悉Teleport,webzip等等网站下载工具,实际上AIX中也完全可以做到这样的功能,那就是利用wget工具。wget是一个命令行工具,用来下载网络文件或者整个网站,它具有自动重试、断点续传、支持代理服务器等等强大的功能。它可以完全替代ftp客户端。wget是在Linux下开发的开放源代码的软件,作者是Hrvoje Niksic,后来被移植到包括Windows在内的各个平台上。IBM在AIX Linux Toolbox中也提供了这个工具,使得它可以在AIX下运行。您可以在IBM网站http://www-1.ibm.com/servers/aix/products/aixos/linux/altlic.html下载到它。IBM提供的wget是1.8.1版本,rpm包格式的。
wget虽然功能强大,但是使用起来还是比较简单的,基本的语法是:wget [参数列表] URL。下面就结合具体的例子来说明一下wget的用法。
1、下载整个http或者ftp站点。
wget http://place.your.url/here
这个命令可以将http://place.your.url/here 首页下载下来。使用-x会强制建立服务器上一模一样的目录,如果使用-nd参数,那么服务器上下载的所有内容都会加到本地当前目录。
wget -r http://place.your.url/here
这个命令会按照递归的方法,下载服务器上所有的目录和文件,实质就是下载整个网站。这个命令一定要小心使用,因为在下载的时候,被下载网站指向的所有地址同样会被下载,因此,如果这个网站引用了其他网站,那么被引用的网站也会被下载下来!基于这个原因,这个参数不常用。可以用-l number参数来指定下载的层次。例如只下载两层,那么使用-l 2。
要是您想制作镜像站点,那么可以使用-m参数,例如:
wget -m http://place.your.url/here
这时wget会自动判断合适的参数来制作镜像站点。此时,wget会登录到服务器上,读入robots.txt并按robots.txt的规定来执行。
2、断点续传。
当文件特别大或者网络特别慢的时候,往往一个文件还没有下载完,连接就已经被切断,此时就需要断点续传。wget的断点续传是自动的,只需要使用-c参数,例如:
wget -c http://the.url.of/incomplete/file
使用断点续传要求服务器支持断点续传。-t参数表示重试次数,例如需要重试100次,那么就写-t 100,如果设成-t 0,那么表示无穷次重试,直到连接成功。-T参数表示超时等待时间,例如-T 120,表示等待120秒连接不上就算超时。
3、批量下载。
如果有多个文件需要下载,那么可以生成一个文件,把每个文件的URL写一行,例如生成文件download.txt,
然后用命令:
wget -i download.txt
这样就会把download.txt里面列出的每个URL都下载下来。(如果列的是文件就下载文件,如果列的是网站,那么下载首页)
4、选择性的下载。
可以指定让wget只下载一类文件,或者不下载什么文件。例如:
wget -m --reject=gif http://target.web.site/subdirectory
表示下载http://target.web.site/subdirectory,但是忽略gif文件。--accept=LIST 可以接受的文件类型,--reject=LIST拒绝接受的文件类型。
5、密码和认证。
wget只能处理利用用户名/密码方式限制访问的网站,可以利用两个参数:
--http-user=USER设置HTTP用户
--http-passwd=PASS设置HTTP密码
对于需要证书做认证的网站,就只能利用其他下载工具了,例如curl。
6、利用代理服务器进行下载。
如果用户的网络需要经过代理服务器,那么可以让wget通过代理服务器进行文件的下载。此时需要在当前用户的目录下创建一个.wgetrc文件。文件中可以设置代理服务器:
http-proxy = 111.111.111.111:8080
ftp-proxy = 111.111.111.111:8080
分别表示http的代理服务器和ftp的代理服务器。如果代理服务器需要密码则使用:
--proxy-user=USER设置代理用户
--proxy-passwd=PASS设置代理密码
这两个参数。
使用参数--proxy=on/off 使用或者关闭代理。
wget还有很多有用的功能,需要用户去挖掘。
备注:wget是免费软件,IBM公司不对wget提供软件维护和技术支持。
posted @ 2008-07-04 01:21 放屁阿狗 阅读(460) | 评论 (0)编辑 收藏

curl是一个利用URL语法在命令行方式下工作的文件传输工具。它支持很多协议:FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE 以及 LDAP。curl同样支持HTTPS认证,HTTP POST方法, HTTP PUT方法, FTP上传, kerberos认证, HTTP上传, 代理服务器, cookies, 用户名/密码认证, 下载文件断点续传, 上载文件断点续传, http代理服务器管道( proxy tunneling), 甚至它还支持IPv6, socks5代理服务器, 通过http代理服务器上传文件到FTP服务器等等,功能十分强大。Windows操作系统下的网络蚂蚁,网际快车(FlashGet)的功能它都可以做到。准确的说,curl支持文件的上传和下载,所以是一个综合传输工具,但是按照传统,用户习惯称curl为下载工具。
curl是瑞典curl组织开发的,您可以访问http://curl.haxx.se/获取它的源代码和相关说明。鉴于curl在Linux上的广泛使用,IBM在AIX Linux Toolbox的光盘中包含了这个软件,并且您可以访问IBM网站http://www-1.ibm.com/servers/aix/products/aixos/linux/altlic.html下载它。curl的最新版本是7.10.8,IBM网站上提供的版本为7.9.3。在AIX下的安装很简单,IBM网站上下载的rpm格式的包。
在http://curl.haxx.se/docs/,您可以下载到UNIX格式的man帮助,里面有详细的curl工具的使用说明。curl的用法为:curl [options] [URL...] 其中options是下载需要的参数,大约有80多个,curl的各个功能完全是依靠这些参数完成的。具体参数的使用,用户可以参考curl的man帮助。
下面,本文就将结合具体的例子来说明怎样利用curl进行下载。
1、获得一张页面
使用命令:curl http://curl.haxx.se
这是最简单的使用方法。用这个命令获得了http://curl.haxx.se指向的页面,同样,如果这里的URL指向的是一个文件或者一幅图都可以直接下载到本地。如果下载的是HTML文档,那么缺省的将不显示文件头部,即HTML文档的header。要全部显示,请加参数 -i,要只显示头部,用参数 -I。任何时候,可以使用 -v 命令看curl是怎样工作的,它向服务器发送的所有命令都会显示出来。为了断点续传,可以使用-r参数来指定传输范围。
2、表单(Form)的获取
在WEB页面设计中,form是很重要的元素。Form通常用来收集并向网站提交信息。提交信息的方法有两种,GET方法和POST方法。先讨论GET方法,例如在页面中有这样一段:
<form method="GET" action="junk.cgi">
<input type=text name="birthyear">
<input type=submit name=press value="OK">
</form>
那么浏览器上会出现一个文本框和一个标为“OK”的按钮。按下这个按钮,表单就用GET方法向服务器提交文本框的数据。例如原始页面是在www.hotmail.com/when/birth.html看到的,然后您在文本框中输入1905,然后按OK按钮,那么浏览器的URL现在应该是:“www.hotmail.com/when/junk.cgi?birthyear=1905&press=OK”
对于这种网页,curl可以直接处理,例如想获取上面的网页,只要输入:
curl "www.hotmail.com/when/junk.cgi?birthyear=1905&press=OK"
就可以了。
表单用来提交信息的第二种方法叫做POST方法,POST方法和GET方法的区别在于GET方法使用的时候,浏览器中会产生目标URL,而POST不会。类似GET,这里有一个网页:
<form method="POST" action="junk.cgi">
<input type=text name="birthyear">
<input type=submit name=press value="OK">
</form>
浏览器上也会出现一个文本框和一个标为“OK”的按钮。按下这个按钮,表单用POST方法向服务器提交数据。这时的URL是看不到的,因此需要使用特殊的方法来抓取这个页面:
curl -d "birthyear=1905&press=OK" www.hotmail.com/when/junk.cgi
这个命令就可以做到。
1995年年末,RFC 1867定义了一种新的POST方法,用来上传文件。主要用于把本地文件上传到服务器。此时页面是这样写的:
<form method="POST" enctype='multipart/form-data' action="upload.cgi">
<input type=file name=upload>
<input type=submit name=press value="OK">
</form>
对于这种页面,curl的用法不同:
curl -F upload=@localfilename -F press=OK [URL]
这个命令的实质是将本地的文件用POST上传到服务器。有关POST还有不少用法,用户可以自己摸索。
3、使用PUT方法。
HTTP协议文件上传的标准方法是使用PUT,此时curl命令使用-T参数:
curl -T uploadfile www.uploadhttp.com/receive.cgi
4、有关认证。
curl可以处理各种情况的认证页面,例如下载用户名/密码认证方式的页面(在IE中通常是出现一个输入用户名和密码的输入框):
curl -u name:password www.secrets.com
如果网络是通过http代理服务器出去的,而代理服务器需要用户名和密码,那么输入:
curl -U proxyuser:proxypassword http://curl.haxx.se
任何需要输入用户名和密码的时候,只在参数中指定用户名而空着密码,curl可以交互式的让用户输入密码。
5、引用。
有些网络资源访问的时候必须经过另外一个网络地址跳转过去,这用术语来说是:referer,引用。对于这种地址的资源,curl也可以下载:
curl -e http://curl.haxx.se daniel.haxx.se
6、指定用户客户端。
有些网络资源首先需要判断用户使用的是什么浏览器,符合标准了才能够下载或者浏览。此时curl可以把自己“伪装”成任何其他浏览器:
curl -A "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)" [URL]
这个指令表示curl伪装成了IE5.0,用户平台是Windows 2000。(对方服务器是根据这个字串来判断客户端的类型的,所以即使使用AIX也无所谓)。使用:
curl -A "Mozilla/4.73 [en] (X11; U; Linux 2.2.15 i686)" [URL]
此时curl变成了Netscape,运行在PIII平台的Linux上了。
7、COOKIES
Cookie是服务器经常使用的一种记忆客户信息的方法。如果cookie被记录在了文件中,那么使用命令:
curl -b stored_cookies_in_file www.cookiesite.com
curl可以根据旧的cookie写出新cookie并发送到网站:
curl -b cookies.txt -c newcookies.txt www.cookiesite.com
8、加密的HTTP――HTTPS。
如果是通过OpenSSL加密的https协议传输的网页,curl可以直接访问:
curl https://that.secure.server.com
9、http认证。
如果是采用证书认证的http地址,证书在本地,那么curl这样使用:
curl -E mycert.pem https://that.secure.server.com

参考读物和注意事项:curl非常博大,用户要想使用好这个工具,除了详细学习参数之外,还需要深刻理解http的各种协议与URL的各个语法。这里推荐几个读物:
RFC 2616 HTTP协议语法的定义。
RFC 2396 URL语法的定义。
RFC 2109 Cookie是怎样工作的。
RFC 1867 HTTP如何POST,以及POST的格式。
curl是免费软件,IBM公司对curl不提供技术支持。


posted @ 2008-07-04 01:21 放屁阿狗 阅读(1119) | 评论 (0)编辑 收藏

Doxygen是基于GPL的开源项目,是一个非常优秀的文档系统,当前支持在大多数unix(包括linux),windows家族,Mac系统上运行,完全支持C++, C, Java, IDL(Corba和Microsoft 家族)语言,部分支持PHP和C#语言,输出格式包括HTML、latex、RTF、ps、PDF、压缩的HTML和unix manpage。有很多开源项目(包括前两篇文章介绍的log4cpp和CppUnit)都使用了doxygen文档系统。而国内的开发人员却使用的不多,这里从开发人员使用的角度介绍这个工具,使开发人员用最少的代价尽快掌握这种技术,并结合这个工具探讨如何撰写注释的问题。以下以linux下的C+ +语言为例进行介绍,以下讨论基于doxygen1.3.3。

1. doxygen使用步骤
由于只是工具的使用,这里不介绍它的原理,直接从使用步骤开始。Doxygen的使用步骤非常简单。主要可以分为:
 1)第一次使用需要安装doxygen的程序
 2)生成doxygen配置文件
 3)编码时,按照某种格式编写注释
 4)生成对应文档
doxygen的安装非常简单, linux下可以直接下载安装包运行即可,下载源代码编译安装也是比较通用的编译安装命令。请参考其安装文档完成安装。

Doxygen在生成文档时可以定义项目属性以及文档生成过程中的很多选项,使用下面命令能够产生一个缺省的配置文件:
doxygen -g  [配置文件名]
可以根据项目的具体需求修改配置文件中对应的项,具体的修改过程在下面介绍。修改过的配置文件可以作为以后项目的模板。

让doxygen自动产生文档,平常的注释风格可不行,需要遵循doxygen自己的格式。具体如何写doxygen认识的注释在第3节详细介绍。

OK,代码编完了,注释也按照格式写好了,最后的文档是如何的哪?非常简单,运行下面的命令,相应的文档就会产生在指定的目录中。
  doxygen [配置文件名]

需要注意的是doxygen并不处理所有的注释,doxygen重点关注与程序结构有关的注释,比如:文件、类、结构、函数、变量、宏等注释,而忽略函数内变量、代码等的注释。

2. doxygen配置文件
doxygen配置文件的格式是也是通常的unix下配置文件的格式:注释'#'开始;tag = value [,value2…];对于多值的情况可以使用 tag += value [,value2…]。

对doxygen的配置文件的修改分为两类:一种就是输出选项,控制如何解释源代码、如何输出;一种就是项目相关的信息,比如项目名称、源代码目录、输出文档目录等。对于第一种设置好后,通常所有项目可以共用一份配置,而后一种是每个项目必须设置的。下面选择重要的,有可能需要修改的选项进行解释说明,其他选项在配置文件都有详细解释。

TAG 缺省值 含义
PROJECT_NAME  项目名称
PROJECT_NUMBER  可以理解为版本信息
OUTPUT_DIRECTORY  输出文件到的目录,相对目录(doxygen运行目录)或者绝对目录
INPUT  代码文件或者代码所在目录,使用空格分割
FILE_PATTERNS *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp *.h++ *.idl *.odl 指定INPUT的目录中特定文件,如:*.cpp *.c *.h
RECURSIVE NO 是否递归INPUT中目录的子目录
EXCLUDE  在INPUT目录中需要忽略的子目录
EXCLUDE_PATTERNS  明确指定的在INPUT目录中需要忽略的文件,如:FromOut*.cpp
 
OUTPUT_LANGUAGE English 生成文档的语言,当前支持2、30种语言,国内用户可以设置为Chinese
USE_WINDOWS_ENCODING YES(win版本)
NO(unix版本) 编码格式,默认即可。
EXTRACT_ALL NO 为NO,只解释有doxygen格式注释的代码;为YES,解析所有代码,即使没有注释。类的私有成员和所有的静态项由EXTRACT_PRIVATE和 EXTRACT_STATIC控制
EXTRACT_PRIVATE NO 是否解析类的私有成员
EXTRACT_STATIC NO 是否解析静态项
EXTRACT_LOCAL_CLASSES YES 是否解析源文件(cpp文件)中定义的类
SOURCE_BROWSER NO 如果为YES,源代码文件会被包含在文档中
INLINE_SOURCES NO 如果为YES,函数和类的实现代码被包含在文档中
ALPHABETICAL_INDEX NO 生成一个字母序的列表,有很多类、结构等项时建议设为YES
GENERATE_HTML YES 是否生成HTML格式文档
GENERATE_HTMLHELP NO 是否生成压缩HTML格式文档(.chm)
GENERATE_LATEX YES 是否乘车latex格式的文档
GENERATE_RTF NO 是否生成RTF格式的文档
GENERATE_MAN NO 是否生成man格式文档
GENERATE_XML NO 是否生成XML格式文档
 

3. doxygen注释
3.1 注释风格
下面是工作量最大部分,安装doxygen格式写注释。通常代码可以附上一个注释块来对代码进行解释,一个注释块由一行或者多行组成。通常一个注释块包括一个简要说明(brief)和一个详细说明(detailed),这两部分都是可选的。可以有多种方式标识出doxygen可识别的注释块。
1)JavaDoc类型的多行注释。
/**
 *  ….text….
 */
2)QT样式的多行注释。
/*!
….text….
 */
3) /// …text….
4) //! …text….
简要说明有多种方式标识,这里推荐使用@brief命令强制说明,例如:
/**
 * @brief [some brief description ]
 *      [ brief description more. ]
 *
 * [some more detailed description…]
 */
以上这些注释格式用来对紧跟其后的代码进行注释。doxygen也允许把注释放到代码后面,具体格式是放一个'<'到注释开始部分。例如:
int var1 ; /**< ….text…. */
int var2; ///< ….text….

注释和代码完全分离,放在其他地方也是允许的,但需要使用特殊的命令加上名称或者声明进行标识,比如:class、struct、union、 enum、fn、var、def、file、namespace、package、interface(这些也就是doxygen关注的注释类型)。这里不推荐使用,建议注释尽量放在代码前后。具体使用方式参见doxygen手册。

3.2 doxygen常用注释格式
通常的选择上面的一、两种注释风格,遇到头文件中各种类型定义,关键变量、宏的定义,在其前或者后使用 @brief 定义其简要说明,空一行后继续写其详细的注释即可。

对函数的注释,是比较常常需要注释的部分。除了定义其简要说明以及详细注释,还可以使用param命令对其各个参数进行注释,使用return命令对返回值进行注释。常见的格式如下:
/**
 *@brief func's brief comment.
 *
 * Some detailed comment.
 *@param a [param a 's comment.]
 *@param b [param b 's comment.]
 *@exception std::out_of_range [exception's comment.]
 *@return [return's comment.]
 */
int func1(int a, int b);

进行设计时,通常有模块的概念,一个模块可能有多个类或者函数组成,完成某个特定功能的代码的集合。如何对这个概念进行注释?doxygen提供了group的概念,生成的模块的注释会单独放在一个模块的页面中。使用下面的格式定义一个group。
/** [group_name] [brief group description ]
 * detailed group description ]
 * @{
*/
code
/** @} */
group中的代码可以有自己的注释。单纯定义一个模块,去除{ 和}命令即可。任何其他代码项(比如类、函数、甚至文件)如果要加入到某个模块,可以在其doxygen注释中使用ingroup命令即可。Group之间使用ingroup命令,可以组成树状关系。
/** @file util.cpp
* @ingroup [group_name]
 * @brief file's brief info.
 */
把多个代码项一起添加到某个模块中可以使用addtogroup命令,格式和defgroup相似。

对于某几个功能类似的代码项(比如类、函数、变量)等,如果希望一起添加注释,而又不想提升到模块的概念,可以通过下面的方式:
//@{
/** Comments for all below code. */
code…
//@}
对这种组进行命名可以使用name命令。此时中间代码可以有自己的注释。如:
/** @name group_name
 * description for group.
 */
//@{
code…
//@}

3.3 doxygen常用注释命令
doxygen通过注释命令识别注释中需要特殊处理的注释,比如函数的参数、返回值进行突出显示。上面也提到了一些注释命令(如:brief、param、return、以及group相关的命令),下面对其他一些常用的注释命令进行解释说明。
@exception <exception-object> {exception description} 对一个异常对象进行注释。
@warning {warning message } 一些需要注意的事情
@todo { things to be done }  对将要做的事情进行注释
@see {comment with reference to other items } 一段包含其他部分引用的注释,中间包含对其他代码项的名称,自动产生对其的引用链接。
@relates <name> 通常用做把非成员函数的注释文档包含在类的说明文档中。
@since {text} 通常用来说明从什么版本、时间写此部分代码。
@deprecated
@pre { description of the precondition } 用来说明代码项的前提条件。
@post { description of the postcondition } 用来说明代码项之后的使用条件。
@code 在注释中开始说明一段代码,直到@endcode命令。
@endcode 注释中代码段的结束。

到此为止,常用的doxygen的注释格式讨论完毕,我们能够按照一定的格式撰写doxygen认识的注释,并能够使用doxygen方便快捷的生成对应的文档,不过注释中应该写些什么,如何撰写有效的注释可能是困扰开发人员的一个更深层次的问题。

4. 注释的书写
注释应该怎么写,写多还是写少。过多的注释甚至会干扰对代码的阅读。写注释的一个总的原则就是注释应该尽量用来表明作者的意图,至少也应该是对一部分代码的总结,而不应该是对代码的重复或者解释。对代码的重复或者解释的代码,看代码可能更容易理解。反映作者意图的注释解释代码的目的,从解决问题的层次上进行注释,而代码总结性注释则是从问题的解答的层次上进行注释。

推荐的写注释的过程是首先使用注释勾勒出代码的主要框架,然后根据注释撰写相应的代码。对各种主要的数据结构、输出的函数、多个函数公用的变量进行详细地注释。对代码中控制结构,单一目的的语句集进行注释。下面是一些写注释时需要注意的要点:
  避免对单独语句进行注释;
  通过注释解释为什么这么做、或者要做什么,使代码的读者可以只阅读注释理解代码;
  对读者可能会有疑问的地方进行注释;
  对数据定义进行注释,而不是对其使用过程进行注释;
  对于难于理解的代码,进行改写,而不要试图通过注释加以说明;
  对关键的控制结构进行注释;
  对数据和函数的边界、使用前提等进行注释;

5. 参考资料
 1. doxygen homepage
 http://www.stack.nl/~dimitri/doxygen/

 2. doxygen manual
 http://www.stack.nl/~dimitri/doxygen/manual.html

 3. Code Complete: A Practical Handbook of Software Construction. Redmond, Wa.: Microsoft Press, 880 pages, 1993. ISBN: 1-55615-484-4.
 
 4. 简介doxygen
 http://www.stack.nl/~dimitri/doxygen/doxygen_intro_cn.html
 
 5. 10 Minutes to document your code
 http://www.codeproject.com/tips/doxysetup.asp

 6. 使用doxygen
 http://www.csdn.net/Develop/article/16%5C16383.shtm
posted @ 2008-07-04 01:20 放屁阿狗 阅读(1830) | 评论 (0)编辑 收藏

     摘要: wxlocale 初始化字符语言种类        locale.Init(wxLANGUAGE_CHINESE );//    locale.Init(wxLANGUAGE_FRENCH );    wxLocale::AddCatalogLookupPathPrefix(wx...  阅读全文
posted @ 2008-07-04 01:19 放屁阿狗 阅读(1264) | 评论 (0)编辑 收藏

     摘要: Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> bh处理 1.三种旧式的bottom half 处理类型 IMMEDIATE_BH:  driver注册入tq_immediate队列,等待调度 TQUEUE_BH:  &n...  阅读全文
posted @ 2008-07-04 01:11 放屁阿狗 阅读(5114) | 评论 (3)编辑 收藏

1.Qgsrenderer 图层绘制器抽象基类

 每个renderer只能绘制某一种适量图形对象, Qgsrenderer ::mVectorType 定义了适量图层类型(QGis::VectorType) ,Qgsrenderer定义了作为一个绘图器的基本功能接口

 

QgsRasterLayer 通过QgsSingleSymbolRenderer实现矢量对象的绘制

 

2 QgsMapLayer 地图图层

 图层类型: VECTOR,RASTER

2.1 QgsVectorLayer矢量图层

       QgsVectorLayer对应的就是QgsRasterLayer , QgsMapLayer的子类图层对象仅仅这两种。

2.2 QgsMapCanvasLayer 画板图层

2.3 QgsMapLayerRegistry

       存储当前所有地图层对象,提供Layer id查询MapLayer对象的功能mapLayer(layid)

2.4 QgsMapCanvasLayer

       QgsMapCanvasLayerqgis应用层的图层对象,用于村促跟用户交互的信息,它并不是Map核心数据对象

 

3.QgsFeature

图层特征对象。QgsFeature包含若干Attribute,一个QgsGeometry对象

 

QgsLine

 QgsLine2QgsPoint构成的线段

4. QgsMapRender

QgsMapCanvasMap利用此对象实现绘制,无子类实现。当canvas产生事件,诸如大小调整、移动、缩放等等请求时,QgsMapCanvas请求QgsMapCanvasMap进行刷新绘制,后者调用QgsMapRender::render()进行绘制图层。

Render(){

       QgsMapRender内部保留图层名称数组,根据图层idQgsMapLayerRegistry中获取图层对象,绘制图层从最底部开始。

      

}

 

5.OgsMapCanvas图层绘制板

N个图层是绘制在QgsMapCanvasMap这个对象之上的,这个对象是Qt的绘制对象

QgsMapCanvasMap其实就是提供OgsMapRender的绘制缓冲,最终还是请求QgsMapRender进行图层绘制。

 

6. QgsProviderRegistry

       Qgis的数据源作为一个provider将被登记在QgsProviderRegistry对象中

 

 

创建一个新的矢量图层

QgsProviderRegistry中查找ogrprovider,创建一个ogr的矢量数据空间createEmptyDataSource

QgsOgrProvider作为QgsVectorDataProvider的子类,QgsVectorDataProvider作为接口被其他模块访问

new QgsVectorLayer(ogr){

       new QgsSingleSymbolRenderer()

}

 

QgsMapToPixel

       地图单位到屏幕像素位置的转换,这个操作非常简单,根据设定的每像素地图大小单位来计算地图单位(world)到屏幕像素的转换,反之亦然

 

 

QgsProject QgsProjectFileTransformQgsversion

这些类用于qgis项目信息管理之用,项目文件类型名.qgs,格式是xmlQgsProjectFileTransform用于不同版本的qgis的功能文件的转换

 

 

QgsSymbol

这个类一方面是实现绘制图层类型的IconQgis中如果创建新的line矢量层,则这个层的图标显示在层显示树的节点的Icon就是用QgsSymbol绘制产生

另一方面,在编辑矢量图层时,添加的线段都有两端的节点点,QgsSymbol就是绘制这个节点

 

矢量图层类型:

    Point,

    Line,

    Polygon,

 

 

QgsMapTip

图层地标提示。当鼠标在MapCanvas上移动时,MapTip被一个定时器驱动,这时MapTip将当前地图坐标作为中心点,围绕这个点产生一个选择区域(  double searchRadius = mpMapCanvas->extent().width() * (QGis::DEFAULT_IDENTIFY_RADIUS / 100.0 );)

这个区域作为搜寻区域提交给ogr-provider查找地图的Feature对象

再找Featrue的可显示的字段,然后用QToolTip进行显示。 QToolTip最终是要被替换掉的

 

QgsFieldMap

typedef QMap<int, QgsField> QgsFieldMap;

矢量图层的对象具有多个属性,可以在添加编辑对象时设置这些属性

 

attributeFields[CmtAttr] = QgsField(attr[CmtAttr], QVariant::String, "text");

以上代码就可以添加一种属性类型

 

 

地图距离单位:

METERS,

FEET,

DEGREES,

 

 

 

Mbr : 可能全称 Max boundary Rectangle

 

坐标转换:

      1.空间坐标转换: long/lat坐标投影到世界地图坐标 QgsCoordinateTransform实现】

       2.世界坐标转换到屏幕坐标【QgsMapToPixel实现】

 

//数学宏

#define PI 3.1415926

//角度转弧度

#define DEG_TO_RAD(ang) ((ang)*PI/180.0)

//弧度转角度

#define RAD_TO_DEG(rad) ((rad)*180.0/PI)

//取得x~y之间任意一个值

#define RAND_RANGE(x,y) ((x) + rand() % ((y) - (x) + 1)))

 

 

GPX

www.gpsbabel.org 是不同gps日志数据的通用解析和转换工具

gps log数据分类:

l         Waypoints: 我的行径上要路过的点。比如我去北京,顺便到徐州拜访朋友,所以途经的徐州是我要停留的waypoint

l         Routes 是指所有WayPoint的集合构成的线路

l         Tracks 我途经的路上所有走过的轨迹点(因该是最多的数据量)

WayPointroute我理解为是面向应用的,而Track就是基础数据,是物理的记录

看看老外的注释

> a) waypoint
 
A geopoint with some special tags like name, comment and the like. 
Usually used to mark special locations as your home, a hotel or a 
geocache. Huge collections of gas stations, post boxes, shops and the 
like are called "points of interest" (aka POIs).
 
> b) track
 
A collection of geopoints recorded by your GPS device while traveling. A 
trackpoint doesn't have a name or comment, but it usually has a 
timestamp. This distibguishes a trackpoint from a waypoint.
 
> c) route
 
A collection of waypoints defining the route you want to pass while 
traveling. 

 

posted @ 2008-07-03 23:48 放屁阿狗 阅读(4459) | 评论 (0)编辑 收藏

仅列出标题
共25页: First 14 15 16 17 18 19 20 21 22 Last