PHP,DDD,CQRS,Event Sourcing,Kubernetes,Docker,Golang

0%

一、inode是什么?

理解inode,要从文件储存说起。

文件储存在硬盘上,硬盘的最小存储单位叫做”扇区”(Sector)。每个扇区储存512字节(相当于0.5KB)。

操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个”块”(block)。这种由多个扇区组成的”块”,是文件存取的最小单位。”块”的大小,最常见的是4KB,即连续八个 sector组成一个 block。

文件数据都储存在”块”中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为”索引节点”。

二、inode的内容

inode包含文件的元信息,具体来说有以下内容:

  * 文件的字节数

  * 文件拥有者的User ID

  * 文件的Group ID

  * 文件的读、写、执行权限

  * 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。

  * 链接数,即有多少文件名指向这个inode

  * 文件数据block的位置

可以用stat命令,查看某个文件的inode信息:

stat example.txt

总之,除了文件名以外的所有文件信息,都存在inode之中。至于为什么没有文件名,下文会有详细解释。

三、inode的大小

inode也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。

每 个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定 在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。

查看每个硬盘分区的inode总数和已经使用的数量,可以使用df命令。

df -i

查看每个inode节点的大小,可以用如下命令:

sudo dumpe2fs -h /dev/hda | grep “Inode size”

由于每个文件都必须有一个inode,因此有可能发生inode已经用光,但是硬盘还未存满的情况。这时,就无法在硬盘上创建新文件。

四、inode号码

每个inode都有一个号码,操作系统用inode号码来识别不同的文件。

这 里值得重复一遍,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或 者绰号。表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号 码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。

使用ls -i命令,可以看到文件名对应的inode号码:

ls -i example.txt

五、目录文件

Unix/Linux系统中,目录(directory)也是一种文件。打开目录,实际上就是打开目录文件。

目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件的文件名,以及该文件名对应的inode号码。

ls命令只列出目录文件中的所有文件名:

ls /etc

ls -i命令列出整个目录文件,即文件名和inode号码:

ls -i /etc

如果要查看文件的详细信息,就必须根据inode号码,访问inode节点,读取信息。ls -l命令列出文件的详细信息。

ls -l /etc

六、硬链接

一 般情况下,文件名和inode号码是”一一对应”关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个 inode号码。这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访 问。这种情况就被称为”硬链接”(hard link)。

ln命令可以创建硬链接:

ln 源文件 目标文件

运 行上面这条命令以后,源文件与目标文件的inode号码相同,都指向同一个inode。inode信息中有一项叫做”链接数”,记录指向该inode的文 件名总数,这时就会增加1。反过来,删除一个文件名,就会使得inode节点中的”链接数”减1。当这个值减到0,表明没有文件名指向这个inode,系 统就会回收这个inode号码,以及其所对应block区域。

这里顺便说一下目录文件的”链接数”。创建目录时, 默认会生成两个目录项:”.”和”..”。前者的inode号码就是当前目录的inode号码,等同于当前目录的”硬链接”;后者的inode号码就是当 前目录的父目录的inode号码,等同于父目录的”硬链接”。所以,任何一个目录的”硬链接”总数,总是等于2加上它的子目录总数(含隐藏目录),这里的 2是父目录对其的“硬链接”和当前目录下的”.硬链接“。

七、软链接

除了硬链接以外,还有 一种特殊情况。文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打 开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的”软链接”(soft link)或者”符号链接(symbolic link)。

这 意味着,文件A依赖于文件B而存在,如果删除了文件B,打开文件A就会报错:”No such file or directory”。这是软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode”链接数”不会因此 发生变化。

ln -s命令可以创建软链接。

ln -s 源文文件或目录 目标文件或目录

八、inode的特殊作用

由于inode号码与文件名分离,这种机制导致了一些Unix/Linux系统特有的现象。

  1. 有时,文件名包含特殊字符,无法正常删除。这时,直接删除inode节点,就能起到删除文件的作用。

  2. 移动文件或重命名文件,只是改变文件名,不影响inode号码。

  3. 打开一个文件以后,系统就以inode号码来识别这个文件,不再考虑文件名。因此,通常来说,系统无法从inode号码得知文件名。

第3点使得软件更新变得简单,可以在不关闭软件的情况下进行更新,不需要重启。因为系统通过inode号码,识别运行中的文件,不通过文件名。更新的时 候,新版文件以同样的文件名,生成一个新的inode,不会影响到运行中的文件。等到下一次运行这个软件的时候,文件名就自动指向新版文件,旧版文件的 inode则被回收。

九 实际问题 在一台配置较低的Linux服务器(内存、硬盘比较小)的/data分区内创建文件时,系统提示磁盘空间不足,用df -h命令查看了一下磁盘使用情况,发现/data分区只使用了66%,还有12G的剩余空间,按理说不会出现这种问题。 后来用df -i查看了一下/data分区的索引节点(inode),发现已经用满(IUsed=100%),导致系统无法创建新目录和文件。 查找原因: /data/cache目录中存在数量非常多的小字节缓存文件,占用的Block不多,但是占用了大量的inode。 解决方案: 1、删除/data/cache目录中的部分文件,释放出/data分区的一部分inode。 2、用软连接将空闲分区/opt中的newcache目录连接到/data/cache,使用/opt分区的inode来缓解/data分区inode不足的问题: ln -s /opt/newcache /data/cache 转自: http://www.ruanyifeng.com/blog/2011/12/inode.html http://blog.s135.com/post/295/ http://hi.baidu.com/leejun_2005/blog/item/d9aa13a53b3af6e99152ee7e.html

 100除以7的余数是2,意思就是说把100个东西七个七个分成一组的话最后还剩2个。余数有一个严格的定义:假如被除数是a,除数是b(假设它们 均为正整数),那么我们总能够找到一个小于b的自然数r和一个整数m,使得a=bm+r。这个r就是a除以b的余数,m被称作商。我们经常用mod来表示 取余,a除以b余r就写成a mod b = r。

如果两个数a和b之差能被m整除,那么我们就说a和b对模数m同余(关于m同余)。比 如,100-60除以8正好除尽,我们就说100和60对于模数8同余。它的另一层含义就是说,100和60除以8的余数相同。a和b对m同余,我们记作 a≡b(mod m)。比如,刚才的例子可以写成100≡60(mod 8)。你会发现这种记号到处都在用,比如和数论相关的书中就经常把a mod 3 = 1写作a≡1(mod 3)。

之所以把同余当作一种运算,是因为同余满足运算的诸多性质。比如,同余满足等价关系。具体地说,它满足自反性(一个数永远和自己同余)、对称性(a和b同余,b和a也就同余)和传递性(a和b同余,b和c同余可以推出a和c同余)。这三个性质都是显然的。

 同余运算里还有稍微复杂一些的性质。比如,同余运算和整数加减法一样满足“等量加等量,其和不变”。小学我们就知道,等式两边可以同时加上一个相等的数。例 如,a=b可以推出a+100=b+100。这样的性质在同余运算中也有:对于同一个模数m,如果a和b同余,x和y同余,那么a+x和b+y也同余。在 我看来,这个结论几乎是显然的。当然,我们也可以严格证明这个定理。这个定理对减法同样有效。 **性质:如果a≡b(mod m),x≡y(mod m),则a+x≡b+y(mod m)。** 证 明:条件告诉我们,可以找到p和q使得a-mp = b-mq,也存在r和s使得x-mr = y-ms。于是a-mp + x-mr = b-mq + y-ms,即a+x-m(p+r) = b+y-m(q+s),这就告诉我们a+x和b+y除以m的余数相同。 容易想到,两个同余式对应相乘,同余式两边仍然相等: **如果a≡b(mod m),x≡y(mod m),则ax≡by(mod m)。** 证明:条件告诉我们,a-mp = b-mq,x-mr = y-ms。于是(a-mp)(x-mr) = (b-mq)(y-ms),等式两边分别展开后必然是ax-m(…) = by-m(…)的形式,这就说明ax≡by(mod m)。 现在你知道为什么有的题要 叫你“输出答案mod xxxxx的结果”了吧,那是为了避免高精度运算,因为这里的结论告诉我们在运算过程中边算边mod和算完后再mod的结果一样。假如a是一个很大的数, 令b=a mod m,那么(a * 100) mod m和(b * 100) mod m的结果是完全一样的,这相当于是在a≡b (mod m)的两边同时乘以100。这些结论其实都很显然,因为同余运算只关心余数(不关心“整的部分”),完全可以每一次运算后都只保留余数。因此,整个运算过 程中参与运算的数都不超过m,避免了高精度的出现。 在证明**_Fermat小定理_**时,我们用到了这样一个定理: **如果ac≡bc(mod m),且c和m互质,则a≡b(mod m)** (就是说同余式两边可以同时除以一个和模数互质的数)。 证明:条件告诉我们,ac-mp = bc-mq,移项可得ac-bc = mp-mq,也就是说(a-b)c = m(p-q)。这表明,(a-b)c里需要含有因子m,但c和m互质,因此只有可能是a-b被m整除,也即a≡b(mod m)。

原文地址:http://www.matrix67.com/blog/archives/236

数据库事务概念

数据库事务必须同时满足 4 个特性:原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)和持久性(Durabiliy),简称为ACID。下面是对每个特性的说明。

  • 原子性:表示组成一个事务的多个数据库操作要么全部成功、要么全部失败。
  • 一致性:事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏。如从A账户转账100元到B账户,不管操作成功与否,A和B的存款总额是不变的。
  • 隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对对方产生干扰。准确地说,并非要求做到完全无干扰,数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发性越弱。
  • 持久性:一旦事务提交成功后,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证能够通过某种机制恢复数据。

其实这四个特性,原子性是最终目的。

数据并发的问题

一个数据库可能拥有多个访问客户端,这些客户端都可以并发方式访问数据库。数据库中的相同数据可能同时被多个事务访问,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性。这些问题可以归结为5类,包括3类数据读问题( 脏读、 不可重复读和 幻象读)以及2类数据更新问题( 第一类丢失更新和 第二类丢失更新)。下面,我们分别通过实例讲解引发问题的场景。

脏读(dirty read)

A事务读取B事务尚未提交的更改数据,并在这个数据的基础上操作。如果恰巧B事务回滚,那么A事务读到的数据根本是不被承认的。来看取款事务和转账事务并发时引发的脏读场景: 15150645_B8Se 在这个场景中,B希望取款500元而后又撤销了动作,而A往相同的账户中转账100元,就因为A事务读取了B事务尚未提交的数据,因而造成账户白白丢失了500元。在Oracle数据库中,不会发生脏读的情况。

不可重复读(unrepeatable read)

不可重复读是指 A事务读取了B事务已经提交的更改数据。假设A在取款事务的过程中,B往该账户转账100元,A两次读取账户的余额发生不一致: 15150645_B8Se 在同一事务中,T4时间点和T7时间点读取账户存款余额不一样。

幻象读(phantom read)

A事务读取B事务提交的新增数据,这时A事务将出现幻象读的问题。幻象读一般发生在计算统计数据的事务中,举一个例子,假设银行系统在同一个事务中,两次统计存款账户的总金额,在两次统计过程中,刚好新增了一个存款账户,并存入100元,这时,两次统计的总金额将不一致: 15150645_d6yw 如果新增数据刚好满足事务的查询条件,这个新数据就进入了事务的视野,因而产生了两个统计不一致的情况。 幻象读和不可重复读是两个容易混淆的概念,前者是指读到了其他已经提交事务的新增数据,而后者是指读到了已经提交事务的更改数据(更改或删除),为了避免这两种情况,采取的对策是不同的,防止读取到更改数据,只需要对操作的数据添加行级锁,阻止操作中的数据发生变化,而防止读取到新增数据,则往往需要添加表级锁——将整个表锁定,防止新增数据(Oracle使用多版本数据的方式实现)。

第一类丢失更新

A事务撤销时,把已经提交的B事务的更新数据覆盖了。这种错误可能造成很严重的问题,通过下面的账户取款转账就可以看出来: 15150645_d6yw A事务在撤销时,“不小心”将B事务已经转入账户的金额给抹去了。

第二类丢失更新

A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失: 15150645_d6yw 上面的例子里由于支票转账事务覆盖了取款事务对存款余额所做的更新,导致银行最后损失了100元,相反如果转账事务先提交,那么用户账户将损失100元。

四种隔离级别

尽管数据库为用户提供了锁的DML操作方式,但直接使用锁管理是非常麻烦的,因此数据库为用户提供了自动锁机制。只要用户指定会话的事务隔离级别,数据库就会分析事务中的SQL语句,然后自动为事务操作的数据资源添加上适合的锁。此外数据库还会维护这些锁,当一个资源上的锁数目太多时,自动进行锁升级以提高系统的运行性能,而这一过程对用户来说完全是透明的。 ANSI/ISO SQL 92标准定义了4个等级的事务隔离级别: 15150645_d6yw 事务的隔离级别和数据库并发性是对立的,两者此增彼长。一般来说,使用READ UNCOMMITED隔离级别的数据库拥有最高的并发性和吞吐量,而使用SERIALIZABLE隔离级别的数据库并发性最低。 Mysql的默认隔离级别时Repeatable Read,即可重复读。

我们知道当插入多条数据的时候insert支持多条语句:

INSERT INTO t_member (id, name, email) VALUES
(1, ‘nick’, ‘nick@126.com’),
(4, ‘angel’,‘angel@163.com’),
(7, ‘brank’,‘ba198@126.com’);

但是对于更新记录,由于update语法不支持一次更新多条记录,只能一条一条执行:

UPDATE t_member SET name=’nick’, email=’nick@126.com’ WHERE id=1;
UPDATE t_member SET name=’angel’, email=’angel@163.com’ WHERE id=4;
UPDATE t_member SET name=’brank’, email=’ba198@126.com’ WHERE id=7;

这里问题就出现了,倘若这个update list非常大时(譬如说5000条),这个执行率可想而知。 这就要介绍一下在MySql中INSERT语法具有一个条件DUPLICATE KEY UPDATE,这个语法和适合用在需要判断记录是否存在,不存在则插入存在则更新的记录。 具体的语法可以参见:http://dev.mysql.com/doc/refman/5.0/en/insert.html 基于上面这种情况,针对更新记录,仍然使用insert语句,不过限制主键重复时,更新字段。如下:

INSERT INTO t_member (id, name, email) VALUES
(1, ‘nick’, ‘nick@126.com’),
(4, ‘angel’,‘angel@163.com’),
(7, ‘brank’,‘ba198@126.com’)
ON DUPLICATE KEY UPDATE name=VALUES(name), email=VALUES(email);

注意:ON DUPLICATE KEY UPDATE只是MySQL的特有语法,并不是SQL标准语法! 原文地址:http://www.crackedzone.com/mysql-muti-sql-not-sugguest-update.html

1.创建分支的意义 创 建分支的意义,比如我们在一个基础平台上进行开发,每个技术小组负责一个子项目,而基础平台也是有可能会继续更改的,这个时候,如果不创建分支,子项目之 间会相互影响,影响最大的就是后期的测试和版本发布,子项目A已经结束,但测试却受到正在进行的子项目B的影响,测试通不过,就别说版本发布了。所以,我 们需要从目前的项目(主干trunk)中创建分支(branch),隔离子项目间的相互影响。 2.svn创建分支原理 在 svn中,创建分支,实际上就是一个版本拷贝(对应copy to…注意:绝不是简单在客户端上copy一个目录,而是svn仓库中copy,文件版本号会增加。),两边做任何修改发生的版本变化,是一套机制。 举例:目前主干版本是100,分支版本是101,主干中增加一个文件,版本为102,分支中再增加一个文件,版本就为103了。两边的版本号是一套,不会 重复。 3.svn创建分支的方法 TortoiseSVN:右键点击工程目录->TortoiseSVN->Branch/tag..菜单,From WC at Url自动为工程svn url,比如https://localhost:8443/svn/fbysss/prj1/trunk,to Url填写https://localhost:8443/svn/fbysss/prj1/branches/branch1。点OK按钮,分支就创建好了。 Subclipse:Team->Branch/tag..,跟上面类似. SVN命令模式:svn copy trunk_path branch_path -m ‘描述’ 举例:svn copy https://localhost:8443/svn/fbysss/prj1/trunk https://localhost:8443/svn/fbysss/prj1/branches/branch1 -m “第一个分支” 注意一点:trunk和branch不能互为子目录,否则就乱套了。 4.分支合并 1)从分支合并到主干 分支开发结束之后,往往需要合并回主干去测试、发布,但分支和主干可能有很多冲突的地方,在合并时经常需要手工解决。 被操作对象:主干 From**主干的打出分支时的版本 **To:分支的Head版本(最新版本) 怎么理解这个From和To呢?似乎跟我们的想当然不太一样:因为我们理解,把分支合并到主干,肯定是From分支,To主干。怎么搞反了呢? 实际上,Svn**认为,我们要合并的,是从主干的某个版本开始,到分支的某个版本结束。两边的版本号实际上是一套系统,不会有重复。**我们从TortoiseSVN Help中也能找到证据:

If you are using this method to merge a feature branch back to trunk, you need to ……..
In the From: field enter the full folder URL of the trunk. This may sound wrong, but remember that the trunk is the start point to which you want to add the branch changes. You may also click … to browse the repository.
In the To: field enter the full folder URL of the feature branch.

2)从主干合并到分支 试想这样的情况:一个项目里面,要独立出来一个子项目,需要单独发布版本,用到了基础框架代码,而基础框架在主干中不断修改完善,这就需要从主干合并到分支。 被操作对象:分支 From:分支的第一个版本(最旧版本) To:主干的Head版本(最新版本) 相当于从分支的第一个版本开始一直到主干最后一个版本结束合并之后,替换分支。 3)从分支合并到分支 有 这样的需求:一个项目中有很多分支,这些分支需要分期上线,有多个工作并行,但每一期之间不能相互影响,这就可以打出几个tag(也是分支),从主干 copy而来。其他主干根据排期分别合并到这些tag中来。比如有prjTag1和prjTag2,model1、model2需要合并到prjTag1 中,model3、model4需要合并到prjTag2中。拿prjTag1举例: 在prjTag1的work copy中,merge From**主干的打出分支时的版本 **To:分支的Head版本(最新版本) 注意:From不是本Tag的某个版本,而是之前主干打出分支时的版本,最终Merge到prjTag1的work copy,而prjTag1是找不到当初打分支时的版本的。 原文地址:http://blog.csdn.net/fbysss/article/details/5437157

前言

随着2012年苹果发布第一款Retina Macbook Pro(以下简称RMBP),Retina屏幕开始进入笔记本行业。两年过去了,RMBP的市场占有率越来越高,且获得了一大批设计师朋友的青睐,网站对于Retina屏幕的适配越来越重要。 如果大家对于Retina适配的重要性不是特别清楚,请看我的两个截图: QQ20140827-1@2x 上图是Google的首页LOGO,我们对比下图SOSO的LOGO: QQ20140827-2@2x 如果大家还是看不出来,请自行访问这两个网站或者下载附件的截图对比。 那么说完了重要性,适配Retina的原理又是什么呢?我们知道,当一个图像在标准设备下全屏显示时,一位图像素对应的就是一设备像素,导致一个完全保 真的显示,因为一个位置像素不能进一步分裂。而当在Retina屏幕下时,他要放大四倍来保持相同的物理像素的大小,这样就会丢失很多细节,造成失真的情 形。换句话说,每一位图像素被乘以四填补相同的物理表面在视网膜屏幕下显示。(摘自《走向视网膜(Retina)的Web时代》) 那么,解决方法相信大家也都听过,就是通过手动制图或以编程的方式制作两种不同的图形,一张是普通屏幕的图片,另外一种是Retina屏幕的图形,而且Retina屏幕下的图片是普通屏幕的两倍像素。 原理虽然简单,在现实中要实现就不仅仅如此,需综合考虑加载速度,浏览器适配等多方面因素,本文就是教大家如何对Retina的屏幕进行适配。

正文

1.直接加载2倍大小的图片。

假如要显示的图片大小为200px*300px,你准备的实际图片大小应该为400px*600px,并且使用以下代码控制即可:

这种方法就解决了Retina显示不清楚的问题,但是在普通屏幕下,这种图片要经过浏览器的压缩,在IE6和IE7上有十分差得显示效果,同时,两倍大小的图片势必会导致页面加载时间加长,用户体验下降,此时,我们可以通过Retina.js(http://retinajs.com/)文件解决:

<img class="pic" src="pic.png" height="200px" width="300px"/>
<script type="text/javascript">
$(document).ready(function () {
if (window.devicePixelRatio > 1) {
var images = $("img.pic");
images.each(function(i) {
var x1 = $(this).attr('src');
var x2 = x1.replace(/(.*)(.w+)/, "$1@2x$2");
$(this).attr('src', x2);
});
}
});
</script>

2.Image-set控制

假如要显示的图片大小为200px*300px,你准备的图片应有两张:一张大小为200px*300px,命名为pic.png;另一张大小为 400px*600px,命名为pic@2x.png(@2x是Retina图标的标准命名方式),然后使用以下css代码控制:

#logo {
background: url(pic.png) 0 0 no-repeat;
background-image: -webkit-image-set(url(pic.png) 1x, url(pic@2x.png) 2x);
background-image: -moz-image-set(url(pic.png) 1x,url(images/pic@2x.png) 2x);
background-image: -ms-image-set(url(pic.png) 1x,url(images/pic@2x.png) 2x);
background-image: -o-image-set(url(url(pic.png) 1x,url(images/pic@2x.png) 2x);
}

或者使用HTML代码控制亦可:

3.使用@media控制

实际是判断屏幕的像素比来取舍是否显示高分辨率图像,代码如下:

@media only screen and (-webkit-min-device-pixel-ratio: 1.5),
       only screen and (min--moz-device-pixel-ratio: 1.5), /* 注意这里的写法比较特殊 */
       only screen and (-o-min-device-pixel-ratio: 3/2),
       only screen and (min-device-pixel-ratio: 1.5) {
#logo {
background-image: url(pic@2x.png);
background-size: 100px auto;
}
}

使用这个的确定就是IE6、7、8不支持@media,所以无效。但是如果你只是支持苹果的RMBP的话,不存在兼容问题,因为MacOS X上压根没有IE!哈哈哈! OK,本文到这里就结束了,介绍了上面的三个办法大家可以各有取舍的使用吧~ 附件:附件 原文地址:http://www.ui.cn/project.php?id=24556

Linux系统中的wget是一个下载文件的工具,它用在命令行下。对于Linux用户是必不可少的工具,我们经常要下载一些软件或从远程服务器恢复备份到本地服务器。wget支持HTTP,HTTPS和FTP协议,可以使用HTTP代理。所谓的自动下载是指,wget可以在用户退出系统的之后在后台执行。这意味这你可以登录系统,启动一个wget下载任务,然后退出系统,wget将在后台执行直到任务完成,相对于其它大部分浏览器在下载大量数据时需要用户一直的参与,这省去了极大的麻烦。 wget 可 以跟踪HTML页面上的链接依次下载来创建远程服务器的本地版本,完全重建原始站点的目录结构。这又常被称作”递归下载”。在递归下载的时 候,wget 遵循Robot Exclusion标准(/robots.txt). wget可以在下载的同时,将链接转换成指向本地文件,以方便离线 浏览。 wget 非常稳定,它在带宽很窄的情况下和不稳定网络中有很强的适应性.如果是由于网络的原因下载失败,wget会不断的尝试,直到整个文件下载完毕。如果是服务器打断下载过程,它会再次联到服务器上从停止的地方继续下载。这对从那些限定了链接时间的服务器上下载大文件非常有用。 1.命令格式: wget [参数] [URL地址] 2.命令功能: 用于从网络上下载资源,没有指定目录,下载资源回默认为当前目录。wget虽然功能强大,但是使用起来还是比较简单: 1)支持断点下传功能;这一点,也是网络蚂蚁和FlashGet当年最大的卖点,现在,Wget也可以使用此功能,那些网络不是太好的用户可以放心了; 2)同时支持FTP和HTTP下载方式;尽管现在大部分软件可以使用HTTP方式下载,但是,有些时候,仍然需要使用FTP方式下载软件; 3)支持代理服务器;对安全强度很高的系统而言,一般不会将自己的系统直接暴露在互联网上,所以,支持代理是下载软件必须有的功能; 4)设置方便简单;可能,习惯图形界面的用户已经不是太习惯命令行了,但是,命令行在设置上其实有更多的优点,最少,鼠标可以少点很多次,也不要担心是否错点鼠标; 5)程序小,完全免费;程序小可以考虑不计,因为现在的硬盘实在太大了;完全免费就不得不考虑了,即使网络上有很多所谓的免费软件,但是,这些软件的广告却不是我们喜欢的。 3.命令参数: 启动参数: -V, –version 显示wget的版本后退出 -h, –help 打印语法帮助 -b, –background 启动后转入后台执行 -e, –execute=COMMAND 执行.wgetrc’格式的命令,wgetrc格式参见/etc/wgetrc或~/.wgetrc 记录和输入文件参数: -o, –output-file=FILE 把记录写到FILE文件中 -a, –append-output=FILE 把记录追加到FILE文件中 -d, –debug 打印调试输出 -q, –quiet 安静模式(没有输出) -v, –verbose 冗长模式(这是缺省设置) -nv, –non-verbose 关掉冗长模式,但不是安静模式 -i, –input-file=FILE 下载在FILE文件中出现的URLs -F, –force-html 把输入文件当作HTML格式文件对待 -B, –base=URL 将URL作为在-F -i参数指定的文件中出现的相对链接的前缀 –sslcertfile=FILE 可选客户端证书 –sslcertkey=KEYFILE 可选客户端证书的KEYFILE –egd-file=FILE 指定EGD socket的文件名 下载参数: –bind-address=ADDRESS 指定本地使用地址(主机名或IP,当本地有多个IP或名字时使用) -t, –tries=NUMBER 设定最大尝试链接次数(0 表示无限制). -O –output-document=FILE 把文档写到FILE文件中 -nc, –no-clobber 不要覆盖存在的文件或使用.#前缀 -c, –continue 接着下载没下载完的文件 –progress=TYPE 设定进程条标记 -N, –timestamping 不要重新下载文件除非比本地文件新 -S, –server-response 打印服务器的回应 –spider 不下载任何东西 -T, –timeout=SECONDS 设定响应超时的秒数 -w, –wait=SECONDS 两次尝试之间间隔SECONDS秒 –waitretry=SECONDS 在重新链接之间等待1…SECONDS秒 –random-wait 在下载之间等待0…2*WAIT秒 -Y, –proxy=on/off 打开或关闭代理 -Q, –quota=NUMBER 设置下载的容量限制 –limit-rate=RATE 限定下载输率 目录参数: -nd –no-directories 不创建目录 -x, –force-directories 强制创建目录 -nH, –no-host-directories 不创建主机目录 -P, –directory-prefix=PREFIX 将文件保存到目录 PREFIX/… –cut-dirs=NUMBER 忽略 NUMBER层远程目录 HTTP 选项参数: –http-user=USER 设定HTTP用户名为 USER. –http-passwd=PASS 设定http密码为 PASS -C, –cache=on/off 允许/不允许服务器端的数据缓存 (一般情况下允许) -E, –html-extension 将所有text/html文档以.html扩展名保存 –ignore-length 忽略 \Content-Length’头域 –header=STRING 在headers中插入字符串 STRING –proxy-user=USER 设定代理的用户名为 USER –proxy-passwd=PASS 设定代理的密码为 PASS –referer=URL 在HTTP请求中包含 `Referer: URL’头 -s, –save-headers 保存HTTP头到文件 -U, –user-agent=AGENT 设定代理的名称为 AGENT而不是 Wget/VERSION –no-http-keep-alive 关闭 HTTP活动链接 (永远链接) –cookies=off 不使用 cookies –load-cookies=FILE 在开始会话前从文件 FILE中加载cookie –save-cookies=FILE 在会话结束后将 cookies保存到 FILE文件中 FTP 选项参数: -nr, –dont-remove-listing 不移走 .listing’文件 -g, –glob=on/off 打开或关闭文件名的 globbing机制 –passive-ftp 使用被动传输模式 (缺省值). –active-ftp 使用主动传输模式 –retr-symlinks 在递归的时候,将链接指向文件(而不是目录) 递归下载参数: -r, –recursive 递归下载--慎用! -l, –level=NUMBER 最大递归深度 (inf 或 0 代表无穷) –delete-after 在现在完毕后局部删除文件 -k, –convert-links 转换非相对链接为相对链接 -K, –backup-converted 在转换文件X之前,将之备份为 X.orig -m, –mirror 等价于 -r -N -l inf -nr -p, –page-requisites 下载显示HTML文件的所有图片 递归下载中的包含和不包含(accept/reject): -A, –accept=LIST 分号分隔的被接受扩展名的列表 -R, –reject=LIST 分号分隔的不被接受的扩展名的列表 -D, –domains=LIST 分号分隔的被接受域的列表 –exclude-domains=LIST 分号分隔的不被接受的域的列表 –follow-ftp 跟踪HTML文档中的FTP链接 –follow-tags=LIST 分号分隔的被跟踪的HTML标签的列表 -G, –ignore-tags=LIST 分号分隔的被忽略的HTML标签的列表 -H, –span-hosts 当递归时转到外部主机 -L, –relative 仅仅跟踪相对链接 -I, –include-directories=LIST 允许目录的列表 -X, –exclude-directories=LIST 不被包含目录的列表 -np, –no-parent 不要追溯到父目录 wget -S –spider url 不下载只显示过程 4.使用实例: 实例1:使用wget下载单个文件 命令: wget http://www.minjieren.com/wordpress-3.1-zh\_CN.zip 说明: 以下的例子是从网络下载一个文件并保存在当前目录,在下载的过程中会显示进度条,包含(下载完成百分比,已经下载的字节,当前下载速度,剩余下载时间)。 实例2:使用wget -O下载并以不同的文件名保存 命令: : wget -O wordpress.zip http://www.minjieren.com/download.aspx?id=1080 说明: wget默认会以最后一个符合”/”的后面的字符来命令,对于动态链接的下载通常文件名会不正确。 错误:下面的例子会下载一个文件并以名称download.aspx?id=1080保存 wget http://www.minjieren.com/download?id=1 即使下载的文件是zip格式,它仍然以download.php?id=1080命令。 正确:为了解决这个问题,我们可以使用参数-O来指定一个文件名: wget -O wordpress.zip http://www.minjieren.com/download.aspx?id=1080 实例3:使用wget –limit -rate限速下载 命令: wget --limit-rate=300k http://www.minjieren.com/wordpress-3.1-zh\_CN.zip 说明: 当你执行wget的时候,它默认会占用全部可能的宽带下载。但是当你准备下载一个大文件,而你还需要下载其它文件时就有必要限速了。 实例4:使用wget -c断点续传 命令: wget -c http://www.minjieren.com/wordpress-3.1-zh\_CN.zip 说明: 使用wget -c重新启动下载中断的文件,对于我们下载大文件时突然由于网络等原因中断非常有帮助,我们可以继续接着下载而不是重新下载一个文件。需要继续中断的下载时可以使用-c参数。 实例5:使用wget -b后台下载 命令: wget -b http://www.minjieren.com/wordpress-3.1-zh\_CN.zip 说明: 对于下载非常大的文件的时候,我们可以使用参数-b进行后台下载。 wget -b http://www.minjieren.com/wordpress-3.1-zh\_CN.zip Continuing in background, pid 1840. Output will be written towget-log’. 你可以使用以下命令来察看下载进度: tail -f wget-log 实例6:伪装代理名称下载 命令: wget –user-agent=”Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16” http://www.minjieren.com/wordpress-3.1-zh\_CN.zip 说明: 有些网站能通过根据判断代理名称不是浏览器而拒绝你的下载请求。不过你可以通过–user-agent参数伪装。 实例7:使用wget –spider测试下载链接 命令: wget –spider URL 说明: 当你打算进行定时下载,你应该在预定时间测试下载链接是否有效。我们可以增加–spider参数进行检查。 wget –spider URL 如果下载链接正确,将会显示 wget –spider URL Spider mode enabled. Check if remote file exists. HTTP request sent, awaiting response… 200 OK Length: unspecified [text/html] Remote file exists and could contain further links, but recursion is disabled – not retrieving. 这保证了下载能在预定的时间进行,但当你给错了一个链接,将会显示如下错误 wget –spider url Spider mode enabled. Check if remote file exists. HTTP request sent, awaiting response… 404 Not Found Remote file does not exist – broken link!!! 你可以在以下几种情况下使用spider参数: 定时下载之前进行检查 间隔检测网站是否可用 检查网站页面的死链接 实例8:使用wget –tries增加重试次数 命令: wget –tries=40 URL 说明: 如果网络有问题或下载一个大文件也有可能失败。wget默认重试20次连接下载文件。如果需要,你可以使用–tries增加重试次数。 实例9:使用wget -i下载多个文件 命令: wget -i filelist.txt 说明: 首先,保存一份下载链接文件 cat > filelist.txt url1 url2 url3 url4 接着使用这个文件和参数-i下载 实例10:使用wget –mirror镜像网站 命令: wget –mirror -p –convert-links -P ./LOCAL URL 说明: 下载整个网站到本地。 –miror:开户镜像下载 -p:下载所有为了html页面显示正常的文件 –convert-links:下载后,转换成本地的链接 -P ./LOCAL:保存所有文件和目录到本地指定目录 实例11:使用wget –reject过滤指定格式下载 命令: wget –reject=gif ur 说明: 下载一个网站,但你不希望下载图片,可以使用以下命令。 实例12:使用wget -o把下载信息存入日志文件 命令: wget -o download.log URL 说明: 不希望下载信息直接显示在终端而是在一个日志文件,可以使用 实例13:使用wget -Q限制总下载文件大小 命令: wget -Q5m -i filelist.txt 说明: 当你想要下载的文件超过5M而退出下载,你可以使用。注意:这个参数对单个文件下载不起作用,只能递归下载时才有效。 实例14:使用wget -r -A下载指定格式文件 命令: wget -r -A.pdf url 说明: 可以在以下情况使用该功能: 下载一个网站的所有图片 下载一个网站的所有视频 下载一个网站的所有PDF文件 实例15:使用wget FTP下载 命令: wget ftp-url wget –ftp-user=USERNAME –ftp-password=PASSWORD url 说明: 可以使用wget来完成ftp链接的下载。 使用wget匿名ftp下载: wget ftp-url 使用wget用户名和密码认证的ftp下载 wget –ftp-user=USERNAME –ftp-password=PASSWORD url 备注:编译安装 使用如下命令编译安装: # tar zxvf wget-1.9.1.tar.gz # cd wget-1.9.1 # ./configure # make # make install

1392117921664 今天终于能把个人博客上的文章及文中图片一起同步到新浪微博了,在此发文纪念一下,并带图同步到我的微博测试。并且与大家一起分享一下经验,想必也有很多朋友在同步到微博的时候,遇到了传图的问题。 此前我都是使用多说评论框的接口同步到微博的,但为了能拥有自己的微博来源小尾巴,就自己开发一个。 首先你当然得有微博开放平台的帐号,并申请通过了网站应用审核。其次就是调用API接口了。 新浪微博有个高级接口,能发布一条微博并指定上传图片的url,这个接口由于需要申请使用,比较麻烦,还不如自己用另一个接口来实现锻炼一下自己。这里我使用的是https://upload.api.weibo.com/2/statuses/upload.json 这个接口(详情见这

必选

类型及范围

说明

source

false

string

采用OAuth授权方式不需要此参数,其他授权方式为必填参数,数值为应用的AppKey。

access_token

false

string

采用OAuth授权方式为必填参数,其他授权方式不需要此参数,OAuth授权后获得。

status

true

string

要发布的微博文本内容,必须做URLencode,内容不超过140个汉字。

visible

false

int

微博的可见性,0:所有人能看,1:仅自己可见,2:密友可见,3:指定分组可见,默认为0。

list_id

false

string

微博的保护投递指定分组ID,只有当visible参数为3时生效且必选。

pic

true

binary

要上传的图片,仅支持JPEG、GIF、PNG格式,图片大小小于5M。

lat

false

float

纬度,有效范围:-90.0到+90.0,+表示北纬,默认为0.0。

long

false

float

经度,有效范围:-180.0到+180.0,+表示东经,默认为0.0。

annotations

false

string

元数据,主要是为了方便第三方应用记录一些适合于自己使用的信息,每条微博可以包含一个或者多个元数据,必须以json字串的形式提交,字串长度不超过512个字符,具体内容可以自定。

rip

false

string

开发者上报的操作用户真实IP,形如:211.156.0.1。

文档规定的参数有以上这些,我只用到了source、status和pic三个参数就行了,当然你可以获得access_token来代替source(本文使用source讲解)。 上传图片等流媒体文件需要使用multipart/form-data编码方式,这和我们平常使用的表单上传文件类似。提交时会向服务器端发出这样的数据(如下代码,已经去除部分不相关的头信息)。

POST / HTTP/1.1
Content-Type:application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: weibo.com
Content-Length: 21
Connection: Keep-Alive
Cache-Control: no-cache
txt1=hello&txt2=world

对于普通的HTML Form POST请求,它会在头信息里使用Content-Length注明内容长度。头信息每行一条,空行之后便是Body,即“内容”(entity)。它的Content-Type是application/x-www-form-urlencoded,这意味着消息内容会经过URL编码,就像在GET请 求时URL里的QueryString那样。txt1=hello&txt2=world 最早的HTTP POST是不支持文件上传的,给编程开发带来很多问题。但是在1995年,ietf出台了rfc1867,也就是《RFC 1867 -Form-based File Upload in HTML》,用以支持文件上传。所以Content-Type的类型扩充了multipart/form-data用以支持向服务器发送二进制数据。因此发送post请求时候,表单 属性enctype共有二个值可选,这个属性管理的是表单的MIME编码: ①application/x-www-form-urlencoded(默认值) ②multipart/form-data 其实form表单在你不写enctype属性时,也默认为其添加了enctype属性值,默认值是enctype=”application/x- www-form-urlencoded”. 通过form表单提交文件操作如下:

浏览器会发送以下数据:

POST /t2/upload.do HTTP/1.1
User-Agent: SOHUWapRebot
Accept-Language: zh-cn,zh;q=0.5
Accept-Charset: GBK,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Content-Length: 60408
Content-Type:multipart/form-data; boundary=ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Host: weibo.com
–ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Content-Disposition: form-data;name=”desc”
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
–ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Content-Disposition: form-data;name=”pic”; filename=”photo.jpg”
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
[图片二进制数据]
–ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC–

先分析一下上面的内容,然后我们来根据这内容来构造发送的数据。 第7行指定发送编码是multipart/form-data,并且指定boundary值为“ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC”,这个值可以随机产生,不难理解,boundary就是来分隔数据的。并且每段分隔时boundary值前面需要加“–”,最后结尾处在boundary值的前后加”–”(看上面代码最后一行)。 每段boundary之内,先是数据描述,再是数据的主体。 QQ截图20140818215404 现在放上我构造的代码,自己琢磨一下就明白了:

function post_to_sina_weibo($post_ID) {
if( wp_is_post_revision($post_ID) ) return;
$get_post_info = get_post($post_ID);
//发布的文章内容,带所有标签格式
$get_post_centent = get_post($post_ID)->post_content;
//用正则表达式抠图
preg_match_all(‘/<img.?(?: |\\t|\\r|\\n)?src=[\‘“]?(.+?)[\‘“]?(?:(?: |\\t|\\r|\\n)+.?)?>/sim’, $get_post_centent, $strResult, PREG_PATTERN_ORDER);
if(count($strResult[1]) > 0)
$imgUrl = $strResult[1][0];//获得第一张图url地址
else
$imgUrl = null;
//去掉文章内的html编码的空格、换行、tab等符号
$get_post_centent = str_replace(“\t”, “ “, str_replace(“\n”, “ “, str_replace(“ ”, “ “, $get_post_centent)));
//获取文章标题
$get_post_title = get_post($post_ID)->post_title;
if ( $get_post_info->post_status == ‘publish’ && $_POST[‘original_post_status’] != ‘publish’ ) {
//使用wordpress内置request请求类,在wp_includes/wp_http.php里
$request = new WP_Http;
//微博文字,格式为“【文章标题】文章摘要132字,全文地址:XXXX”
$status = ‘【’ . strip_tags( $get_post_title ) . ‘】 ‘ . mb_strimwidth(strip_tags( apply_filters(‘the_content’, $get_post_centent)),0, 132,’…’) . ‘ 全文地址:’ . get_permalink($post_ID) ;
//如果没图片则请求这个api
$api_url = ‘https://api.weibo.com/2/statuses/update.json';
//body内容,source参数为你的appkey,详情见wordpress的WP_http类
$body = array( ‘status’ => $status, ‘source’=>’706960568’);
//请将xxxxxxxxx替换成你的,xxxxxxxxxx为“用户名:密码”的base64编码
$headers = array( ‘Authorization’ => ‘Basic ‘ . ‘xxxxxxxxxxxxxxxx’ );
//重要步骤,如果文章有图片则构造发送数据包
if($imgUrl!==null)
{
$body[‘pic’] = $imgUrl;
uksort($body, ‘strcmp’);
$str_b=uniqid(‘——————‘);
$str_m=’–’.$str_b;
$str_e=$str_m. ‘–’;
$tmpbody=’’;
//对参数遍历,进行构造内容,仔细阅读,你会发现和上面讲的类似
foreach($body as $k=>$v){
if($k==’pic’){
$img_c=file_get_contents($imgUrl);
$url_a=explode(‘?’, basename($imgUrl));
$img_n=$url_a[0];
$tmpbody.=$str_m.”\r\n”;
$tmpbody.=’Content-Disposition: form-data; name=”‘.$k.’”; filename=”‘.$img_n.’”‘.”\r\n”;
$tmpbody.=”Content-Type: image/unknown\r\n\r\n”;
$tmpbody.=$img_c.”\r\n”;
}else{
$tmpbody.=$str_m.”\r\n”;
$tmpbody.=’Content-Disposition: form-data; name=”‘.$k.”\“\r\n\r\n”;
$tmpbody.=$v.”\r\n”;
}
}
$tmpbody.=$str_e;
$body = $tmpbody;
//图片处理结束,使用uploade API
$api_url = ‘https://upload.api.weibo.com/2/statuses/upload.json';
//设置Content-Type编码为multipart/form-data
$headers[‘Content-Type’] = ‘multipart/form-data; boundary=’.$str_b;
}
//请求接口发送数据,详见wordpress自带的WP_http类
$result = $request->post( $api_url , array( ‘body’ => $body, ‘headers’ => $headers ) );
}
//最后将这个函数挂到pulish_post钩子上,发表文章后就会触发这个函数
add_action(‘publish_post’, ‘post_to_sina_weibo’, 0);

这段代码理论上来说你可以拿去直接用了,只需按注释上说的修改几个参数为自己的就行了。 这也是我研究琢磨出来的,有任何疑问可以一起探讨。

昨天在做项目的时候,因为涉及到数据表结构的改动,需要进行大量数据的导入,那么如何高效的进行是我比较关注的。本文暂且从使用PHP脚本层面上来说,因为使用其他语言或其他方式也可以进行数据的重导。 在讨论这个问题的时候,前辈给我的意见就是单独做一个脚本,如果能拿python做更好,当然,python我还不是很会,所以拿php也能完成。既然这样,就采用最原生的方式来进行数据的重导。 整个过程中要先从一张表中取出全部数据,再进行数据的处理后导入到新的数据表中。而就我测试的那个数据库中的那张表里,数据量就已经上万了,如果直接全部取出必定会有性能上的问题。 他人给我的建议是,使用mysql_connect后用mysql_query执行sql(取大数据的情况如sele ct * from tbl)语句,理由是mysql_query不是返回的数据结果,因为后面用到mysql_fetch_assoc之类的函数,进行游标的移动来取得数据。并在sql执行前后分别使用了memory_get_usage来查看内存使用量。当然,执行后内存使用量比执行前大了,虽然使用了不多的内存,但对于只是测试数据库里的数据而言还行,当数据处理量很大的时候,php程序脚本可能会崩溃。为了确保重导真正线上数据库的数据万无一失,我还是查了一些相关资料。 最后了解到PHP中有个mysql_unbuffered_query这个函数,与mysql_query有点类似,手册上写了该函数不缓存的查询结果,它所带来的好处就是一不用缓存结果,二就是不必等待全部查询后进行操作,而是直接获取一条数据就可以操作。而mysql_query是查询出所有符合条件的结果并缓存后才能进行操作,这就让我怀疑之前建议的那个方法。 于是我同样用mysql_unbuffered_query查询同样一条sql语句,也同样对执行前后查看了内存使用量,结果前后内存使用量没有变,这就说明了内存并没有被拿来做查询数据缓存的相关事情。当然也可以使用以下方法来测试:

$link = mysql_con nect(‘localhost’,’root’,’root’);
mysql_select_ db(‘phpcms’);
$sql = “SEL ECT * FROM `phpcms_content`“;
//$result = mysql_unbuffered_query($sql,$link);
$result = mysql_query($sql,$link);
while ($row = mysql_fe tch_array($result, MYSQL_NUM)) {
printf (“ID: %s Name: %s”, $row[0], $row[1]);
}
mysql_data_seek($result,0);
echo “
“;
while ($row = mysql_fetch_array($result, MYSQL_NUM)) {
printf (“ID: %s Name: %s”, $row[0], $row[1]);
}
mysql_free_result($result);

你会发现使用mysql_query会输出两次,而mysql_unbuffered_query只输出一次,说明使用mysql_query查询结果必定被缓存了,而使用mysql_unbuffered_query则边进行查询边给出结果。 最后再说明,使用mysql_unbuffered_query的话,不能使用mysql_num_rows()和mysql_data_seek()这也正因为它的特性方式决定了这样。还有比较重要的一点,如果你只是单单获取大数据量使用这个函数可以,但是,如果你在取得大数据的时候,使用while($row = mysql_fetch_assoc($result))方式进行执行新的sql语句的话,会出错。手册上也写明了,在执行一条心的sql之前,要提取所有未缓存的sql查询所产生的行。所以只使用mysql_unbuffered_query取数据可以,但期间还要执行其他sql就不行了。 以上只是我对这个函数的初步认识,如果理解有误,也希望能指正交流。

前言

PHP$_SERVER数组中存在五个和路径相关的变量:PHP_SELFSCRIPT_NAMESCRIPT_FILENAMEPATH_INFOREQUEST_URI,这五个变量经常会被混淆,做下区分。

测试环境

Nginx0.8.54 + FastCGI + PHP5.3.4 要先配置Nginx的PATH_INFO,在nginx.conf中加入如下配置:

location ~ .* .php {
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    #从$fastcgi_script_name中分离出真正执行的脚本名称和PATH_INFO
    set $real_script_name $fastcgi_script_name;
    if ($fastcgi_script_name ~ "^(.+?.php)(/.+)$") {
        set $real_script_name $1;
        set $path_info $2;
     }
    #重新设置SCRIPT_FILENAME
    fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
    fastcgi_param  QUERY_STRING       $query_string;
    fastcgi_param  REQUEST_METHOD     $request_method;
    fastcgi_param  CONTENT_TYPE       $content_type;
    fastcgi_param  CONTENT_LENGTH     $content_length;
    #重新设置SCRIPT_NAME
    fastcgi_param SCRIPT_NAME $real_script_name;
    fastcgi_param PATH_INFO $path_info;
    fastcgi_param  REQUEST_URI        $request_uri;
    fastcgi_param  DOCUMENT_URI       $document_uri;
    fastcgi_param  DOCUMENT_ROOT      $document_root;
    fastcgi_param  SERVER_PROTOCOL    $server_protocol;
    fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
    fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
    fastcgi_param  REMOTE_ADDR        $remote_addr;
    fastcgi_param  REMOTE_PORT        $remote_port;
    fastcgi_param  SERVER_ADDR        $server_addr;
    fastcgi_param  SERVER_PORT        $server_port;
    fastcgi_param  SERVER_NAME        $server_name;
    # PHP only, required if PHP was built with --enable-force-cgi-redirect
    fastcgi_param  REDIRECT_STATUS    200;
}

我们的根目录为/var/www,测试域名为example.com(不过这个域名只能改hosts文件YY一下了),结构如下:

var
 |---www
       |---test
             |---test.php

测试脚本

使用如下脚本进行测试:

<?php
    echo 'SCRIPT_NAME=' . $_SERVER['SCRIPT_NAME'] . '<br />';
    echo 'SCRIPT_FILENAME=' . $_SERVER['SCRIPT_FILENAME'] . '<br />';
    echo 'PATH_INFO=' . $_SERVER['PATH_INFO'] . '<br />';
    echo 'REQUEST_URI=' . $_SERVER['REQUEST_URI'] . '<br />';
?>

测试结果

  • PHP_SELF: 当前所执行的脚本的文件名,这个值是相对于根目录来说。 如果请求http://example.com/test/test.php?k=v,则PHP_SELF的值为 /test/test.php
  • SCRIPT_NAME: 当前执行的脚本的路径。 如果请求http://example.com/test/test.php?k=v,则SCRIPT_NAME的值 为/test/test.php。这个变量是在CGI/1.1中定义的。
  • SCRIPT_FILENAME: 当前执行的脚本的绝对路径。 如果请求http://example.com/test/test.php?k=v,则SCRIPT_FILENAME的值 为/var/www/test/test.php。 注意:如果一个脚本以相对路径,CLI方式来执行,例如../test/test.php,那么 $_SERVER['SCRIPT_FILENAME']的值为相对路径,即../test/test.php
  • PATH_INFO:客户端提供的路径信息,即在实际执行脚本后面尾随的内容,但是会去掉Query String。 如果请求http://example.com/test/test.php/a/b?k=v,则PATH_INFO的值为/a/bCGI1.1标准中如下描述:”The PATH_INFO string is the trailing part of thecomponent of the script URI that follows the SCRIPT_NAME part of the path.
  • REQUEST_URI:包含HTTP协议中定义的URI内容。 如果请求http://example.com/test/test.php?k=v,则REQUEST_URI/test/test.php?k=v

区别

  • PHP_SELF VS SCRIPT_NAME: PHP_SELFSCRIPT_NAME的值在大部分情况下都是一样的,但是访问 http://example.com/test/test.php/a/b?k=v这类URL时候,PHP_SELF/test/test.php/a/bSCRIPT_NAME/test/test.php,可以看出PHP_SELFSCRIPT_NAME多了PATH_INFO的内容。

  • REQUEST_URI VS SCRIPT_NAME: 在访问http://example.com/test/test.php?k=v后,REQUEST_URI/test/test.php?k=vSCRIPT_NAME/test/test.php,可以看出REQUEST_URISCRIPT_NAME多了Query String。 如果http://example.com/test/test.php在服务器端做了rewrite

    rewrite /test/test.php /test/test2.php;
那么`REQUEST_URI`为**/test/test.php**,`SCRIPT_NAME`为**/test/test2.php**。

参考

http://tools.ietf.org/html/draft-robinson-www-interface-00 http://stackoverflow.com/questions/279966/php-self-vs-path-info-vs-script-name-vs-request-uri http://ca.php.net/manual/en/reserved.variables.server.php (完)