<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>喵了个咪</title>
	<atom:link href="http://www.hdj.me/feed" rel="self" type="application/rss+xml" />
	<link>http://www.hdj.me</link>
	<description>专注PHP+MYSQL开发</description>
	<lastBuildDate>Thu, 10 May 2012 02:47:22 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>PHP多进程编程</title>
		<link>http://www.hdj.me/php-multithread</link>
		<comments>http://www.hdj.me/php-multithread#comments</comments>
		<pubDate>Thu, 10 May 2012 02:46:28 +0000</pubDate>
		<dc:creator>huangdijia</dc:creator>
				<category><![CDATA[PHP/PHP框架]]></category>
		<category><![CDATA[php多线程]]></category>
		<category><![CDATA[php多进程]]></category>

		<guid isPermaLink="false">http://www.hdj.me/?p=967</guid>
		<description><![CDATA[使用PHP真正的多进程运行模式，适用于数据采集、邮件群发、数据源更新、tcp服务器等环节。 PHP有一组进程控制函数(编译时需要 –enable-pcntl与posix扩展)，使得php能在*nix系统中实现跟c一样的创建子进程、使用exec函数执行程序、处理信号等功能。 PCNTL使用ticks来作为信号处理机制（signal handle callback mechanism），可以最小程度地降低处理异步事件时的负载。何谓ticks？Tick 是一个在代码段中解释器每执行 N 条低级语句就会发生的事件，这个代码段需要通过declare来指定。 常用的PCNTL函数 1. pcntl_alarm ( int $seconds ) 设置一个$seconds秒后发送SIGALRM信号的计数器 2. pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls ] ) 为$signo设置一个处理该信号的回调函数。下面是一个隔5秒发送一个SIGALRM信号，并由signal_handler函数获取，然后打印一个“Caught SIGALRM”的例子： &#60;?php declare(ticks = 1); function signal_handler($signal) { print &#34;Caught SIGALRM\n&#34;; pcntl_alarm(5); } pcntl_signal(SIGALRM, &#34;signal_handler&#34;, true); pcntl_alarm(5); for(;;) { } ?&#62; 3. pcntl_exec [...]]]></description>
			<content:encoded><![CDATA[<p>使用PHP真正的多进程运行模式，适用于数据采集、邮件群发、数据源更新、tcp服务器等环节。</p>
<p>PHP有一组进程控制函数(编译时需要 –enable-pcntl与posix扩展)，使得php能在*nix系统中实现跟c一样的创建子进程、使用exec函数执行程序、处理信号等功能。 PCNTL使用ticks来作为信号处理机制（signal handle callback mechanism），可以最小程度地降低处理异步事件时的负载。何谓ticks？Tick 是一个在代码段中解释器每执行 N 条低级语句就会发生的事件，这个代码段需要通过declare来指定。<span id="more-967"></span></p>
<p>常用的PCNTL函数<br />
1. pcntl_alarm ( int $seconds )<br />
设置一个$seconds秒后发送SIGALRM信号的计数器</p>
<p>2. pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls ] )<br />
为$signo设置一个处理该信号的回调函数。下面是一个隔5秒发送一个SIGALRM信号，并由signal_handler函数获取，然后打印一个“Caught SIGALRM”的例子：</p>
<pre>
&lt;?php
declare(ticks = 1);  

function signal_handler($signal) {
	print &quot;Caught SIGALRM\n&quot;;
    pcntl_alarm(5);
}  

pcntl_signal(SIGALRM, &quot;signal_handler&quot;, true);
pcntl_alarm(5);  

for(;;) {
}  

?&gt;</pre>
<p>3. pcntl_exec ( string $path [, array $args [, array $envs ]] )<br />
在当前的进程空间中执行指定程序，类似于c中的exec族函数。所谓当前空间，即载入指定程序的代码覆盖掉当前进程的空间，执行完该程序进程即结束。</p>
<pre>&lt;?php
$dir = &#039;/home/shankka/&#039;;
$cmd = &#039;ls&#039;;
$option = &#039;-l&#039;;
$pathtobin = &#039;/bin/ls&#039;;  

$arg = array($cmd, $option, $dir);  

pcntl_exec($pathtobin, $arg);
echo &#039;123&#039;;    //不会执行到该行
?&gt;</pre>
<p>4. pcntl_fork ( void )<br />
为当前进程创建一个子进程，并且先运行父进程，返回的是子进程的PID，肯定大于零。在父进程的代码中可以用 pcntl_wait（&#038;$status）暂停父进程知道他的子进程有返回值。注意：父进程的阻塞同时会阻塞子进程。但是父进程的结束不影响子进程的运行。<br />
父进程运行完了会接着运行子进程，这时子进程会从执行pcntl_fork（）的那条语句开始执行（包括此函数），但是此时它返回的是零（代表这是一个子进程）。在子进程的代码块中最好有exit语句，即执行完子进程后立即就结束。否则它会又重头开始执行这个脚本的某些部分。</p>
<p>注意两点：<br />
1. 子进程最好有一个exit；语句，防止不必要的出错；<br />
2. pcntl_fork间最好不要有其它语句，例如：</p>
<pre>&lt;?php
$pid = pcntl_fork();
//这里最好不要有其他的语句
if ($pid == -1) {
	die(&#039;could not fork&#039;);
} else if ($pid) {
	// we are the parent
pcntl_wait($status); //Protect against Zombie children
} else {
	// we are the child
}
?&gt;</pre>
<p>5. pcntl_wait ( int &#038;$status [, int $options ] )<br />
阻塞当前进程，只到当前进程的一个子进程退出或者收到一个结束当前进程的信号。使用$status返回子进程的状态码，并可以指定第二个参数来说明是否以阻塞状态调用：<br />
1. 阻塞方式调用的，函数返回值为子进程的pid,如果没有子进程返回值为-1；<br />
2. 非阻塞方式调用，函数还可以在有子进程在运行但没有结束的子进程时返回0。</p>
<p>6. pcntl_waitpid ( int $pid , int &#038;$status [, int $options ] )<br />
功能同pcntl_wait，区别为waitpid为等待指定pid的子进程。当pid为-1时pcntl_waitpid与pcntl_wait 一样。在pcntl_wait和pcntl_waitpid两个函数中的$status中存了子进程的状态信息，这个参数可以用于 pcntl_wifexited、pcntl_wifstopped、pcntl_wifsignaled、pcntl_wexitstatus、 pcntl_wtermsig、pcntl_wstopsig、pcntl_waitpid这些函数。<br />
例如：</p>
<pre>
&lt;?php
$pid = pcntl_fork();
if($pid) {
    pcntl_wait($status);
    $id = getmypid();
    echo &quot;parent process,pid {$id}, child pid {$pid}\n&quot;;
}else{
    $id = getmypid();
    echo &quot;child process,pid {$id}\n&quot;;
    sleep(2);
}
?&gt;</pre>
<p>子进程在输出child process等字样之后sleep了2秒才结束，而父进程阻塞着直到子进程退出之后才继续运行。</p>
<p>7. pcntl_getpriority ([ int $pid [, int $process_identifier ]] )<br />
取得进程的优先级，即nice值，默认为0，在我的测试环境的linux中（CentOS release 5.2 (Final)），优先级为-20到19，-20为优先级最高，19为最低。（手册中为-20到20）。</p>
<p>8. pcntl_setpriority ( int $priority [, int $pid [, int $process_identifier ]] )<br />
设置进程的优先级。</p>
<p>9. posix_kill<br />
可以给进程发送信号</p>
<p>10. pcntl_singal<br />
用来设置信号的回调函数 </p>
<p>当父进程退出时，子进程如何得知父进程的退出<br />
当父进程退出时，子进程一般可以通过下面这两个比较简单的方法得知父进程已经退出这个消息：</p>
<p>1. 当父进程退出时，会有一个INIT进程来领养这个子进程。这个INIT进程的进程号为1，所以子进程可以通过使用getppid()来取得当前父进程的pid。如果返回的是1，表明父进程已经变为INIT进程，则原进程已经推出。<br />
2. 使用kill函数，向原有的父进程发送空信号（kill(pid, 0)）。使用这个方法对某个进程的存在性进行检查，而不会真的发送信号。所以，如果这个函数返回-1表示父进程已经退出。</p>
<p>除了上面的这两个方法外，还有一些实现上比较复杂的方法，比如建立管道或socket来进行时时的监控等等。</p>
<p>PHP多进程采集数据的例子</p>
<pre>
&lt;?php
/**
* Project: Signfork: php多线程库
* File:    Signfork.class.php
*/

class Signfork{
  /**
   * 设置子进程通信文件所在目录
   * @var string
   */
  private $tmp_path=&#039;/tmp/&#039;;

/**
  * Signfork引擎主启动方法
  * 1、判断$arg类型,类型为数组时将值传递给每个子进程;类型为数值型时,代表要创建的进程数.
  * @param object $obj 执行对象
  * @param string&amp;#124;array $arg 用于对象中的__fork方法所执行的参数
  * 如:$arg,自动分解为:$obj-&gt;__fork($arg[0])、$obj-&gt;__fork($arg[1])...
  * @return array  返回   array(子进程序列=&gt;子进程执行结果);
  */
  public function run($obj,$arg=1){
    if(!method_exists($obj,&#039;__fork&#039;)){
      exit(&quot;Method &#039;__fork&#039; not found!&quot;);
    }

    if(is_array($arg)){
     $i=0;
     foreach($arg as $key=&gt;$val){
       $spawns[$i]=$key;
       $i++;
       $this-&gt;spawn($obj,$key,$val);
     }
     $spawns[&#039;total&#039;]=$i;
    }elseif($spawns=intval($arg)){
      for($i = 0; $i &lt; $spawns; $i++){
        $this-&gt;spawn($obj,$i);
      }
    }else{
      exit(&#039;Bad argument!&#039;);
    }

   if($i&gt;1000) exit(&#039;Too many spawns!&#039;);
      return $this-&gt;request($spawns);
   }

  /**
   * Signfork主进程控制方法
   * 1、$tmpfile 判断子进程文件是否存在，存在则子进程执行完毕，并读取内容
   * 2、$data收集子进程运行结果及数据，并用于最终返回
   * 3、删除子进程文件
   * 4、轮询一次0.03秒，直到所有子进程执行完毕，清理子进程资源
   * @param  string&amp;#124;array $arg 用于对应每个子进程的ID
   * @return array  返回   array([子进程序列]=&gt;[子进程执行结果]);
   */
   private function request($spawns){
     $data=array();
     $i=is_array($spawns)?$spawns[&#039;total&#039;]:$spawns;
     for($ids = 0; $ids&lt;$i; $ids++){
       while(!($cid=pcntl_waitpid(-1, $status, WNOHANG)))usleep(30000);
       $tmpfile=$this-&gt;tmp_path.&#039;sfpid_&#039;.$cid;
       $data[$spawns[&#039;total&#039;]?$spawns[$ids]:$ids]=file_get_contents($tmpfile);
       unlink($tmpfile);
     }
     return $data;
   }

/**
  * Signfork子进程执行方法
  * 1、pcntl_fork 生成子进程
  * 2、file_put_contents 将&#039;$obj-&gt;__fork($val)&#039;的执行结果存入特定序列命名的文本
  * 3、posix_kill杀死当前进程
  * @param object $obj        待执行的对象
  * @param object $i                子进程的序列ID，以便于返回对应每个子进程数据
  * @param object $param 用于输入对象$obj方法&#039;__fork&#039;执行参数
  */
  private function spawn($obj,$i,$param=null){
    if(pcntl_fork()===0){
      $cid=getmypid();
      file_put_contents($this-&gt;tmp_path.&#039;sfpid_&#039;.$cid,$obj-&gt;__fork($param));
      posix_kill($cid, SIGTERM);
      exit;
    }
  }
}
?&gt;</pre>
<p>php在pcntl_fork()后生成的子进程(通常为僵尸进程)必须由pcntl_waitpid()函数进行资源释放。但在 pcntl_waitpid()不一定释放的就是当前运行的进程，也可能是过去生成的僵尸进程(没有释放)；也可能是并发时其它访问者的僵尸进程。但可以使用posix_kill($cid, SIGTERM)在子进程结束时杀掉它。</p>
<p>子进程会自动复制父进程空间里的变量。</p>
<p>PHP多进程编程示例2</p>
<pre>
&lt;?php
//.....
//需要安装pcntl的php扩展，并加载它
if(function_exists(&quot;pcntl_fork&quot;)){
   //生成子进程
  $pid = pcntl_fork();
  if($pid == -1){
    die(&#039;could not fork&#039;);
  }else{
    if($pid){
      $status = 0;
      //阻塞父进程，直到子进程结束，不适合需要长时间运行的脚本，可使用pcntl_wait($status, 0)实现非阻塞式
      pcntl_wait($status);
      // parent proc code
      exit;
    }else{
      // child proc code
      //结束当前子进程，以防止生成僵尸进程
      if(function_exists(&quot;posix_kill&quot;)){
        posix_kill(getmypid(), SIGTERM);
      }else{
        system(&#039;kill -9&#039;. getmypid());
      }
      exit;
    }
  }
}else{
   // 不支持多进程处理时的代码在这里
}
//.....
?&gt;
</pre>
<p>如果不需要阻塞进程，而又想得到子进程的退出状态，则可以注释掉pcntl_wait($status)语句，或写成：</p>
<pre>
&lt;?php
pcntl_wait($status, 1);
//或
pcntl_wait($status, WNOHANG);
?&gt;
</pre>
<p>在上面的代码中，如果父进程退出(使用exit函数退出或redirect)，则会导致子进程成为僵尸进程(会交给init进程控制)，子进程不再执行。</p>
<p>僵尸进程是指的父进程已经退出,而该进程dead之后没有进程接受,就成为僵尸进程.(zombie)进程。任何进程在退出前(使用exit退出) 都会变成僵尸进程(用于保存进程的状态等信息)，然后由init进程接管。如果不及时回收僵尸进程，那么它在系统中就会占用一个进程表项，如果这种僵尸进程过多，最后系统就没有可以用的进程表项，于是也无法再运行其它的程序。</p>
<p>预防僵尸进程有以下几种方法：</p>
<p>   1. 父进程通过wait和waitpid等函数使其等待子进程结束，然后再执行父进程中的代码，这会导致父进程挂起。上面的代码就是使用这种方式实现的，但在WEB环境下，它不适合子进程需要长时间运行的情况(会导致超时)。<br />
   使用wait和waitpid方法使父进程自动回收其僵尸子进程(根据子进程的返回状态)，waitpid用于临控指定子进程，wait是对于所有子进程而言。<br />
   2. 如果父进程很忙，那么可以用signal函数为SIGCHLD安装handler，因为子进程结束后，父进程会收到该信号，可以在handler中调用wait回收<br />
   3. 如果父进程不关心子进程什么时候结束，那么可以用signal(SIGCHLD, SIG_IGN)通知内核，自己对子进程的结束不感兴趣，那么子进程结束后，内核会回收，并不再给父进程发送信号，例如：</p>
<pre>
&lt;?php
pcntl_signal(SIGCHLD, SIG_IGN);
$pid = pcntl_fork();
//....code
?&gt;</pre>
<p>   4. 还有一个技巧，就是fork两次，父进程fork一个子进程，然后继续工作，子进程再fork一个孙进程后退出，那么孙进程被init接管，孙进程结束后，init会回收。不过子进程的回收还要自己做。下面是一个例子：</p>
<pre>
#include &quot;apue.h&quot;
#include &lt;sys/wait.h&gt;

int main(void){
pid_t    pid;

if ((pid = fork()) &lt; 0){
   err_sys(&quot;fork error&quot;);
} else if (pid == 0){     /**//* first child */
  if ((pid = fork()) &lt; 0){
     err_sys(&quot;fork error&quot;);
  }elseif(pid &gt; 0){
     exit(0);    /**//* parent from second fork == first child */
  }

  /**
   * We&#039;re the second child; our parent becomes init as soon
   * as our real parent calls exit() in the statement above.
   * Here&#039;s where we&#039;d continue executing, knowing that when
   * we&#039;re done, init will reap our status.
   */
   sleep(2);
   printf(&quot;second child, parent pid = %d &quot;, getppid());
   exit(0);
}

if (waitpid(pid, NULL, 0) != pid)  /**//* wait for first child */
  err_sys(&quot;waitpid error&quot;);

/**
 * We&#039;re the parent (the original process); we continue executing,
 * knowing that we&#039;re not the parent of the second child.
 */
 exit(0);
}
</pre>
<p>      在fork()/execve()过程中，假设子进程结束时父进程仍存在，而父进程fork()之前既没安装SIGCHLD信号处理函数调用 waitpid()等待子进程结束，又没有显式忽略该信号，则子进程成为僵尸进程，无法正常结束，此时即使是root身份kill-9也不能杀死僵尸进程。补救办法是杀死僵尸进程的父进程(僵尸进程的父进程必然存在)，僵尸进程成为”孤儿进程”，过继给1号进程init，init会定期调用wait回收清理这些父进程已退出的僵尸子进程。</p>
<p>      所以，上面的示例可以改成：</p>
<pre>
&lt;?php
//.....
//需要安装pcntl的php扩展，并加载它
if(function_exists(&quot;pcntl_fork&quot;)){
 //生成第一个子进程
$pid = pcntl_fork();  //$pid即所产生的子进程id
if($pid == -1){
  //子进程fork失败
  die(&#039;could not fork&#039;);
}else{
  if($pid){
    //父进程code
    sleep(5);  //等待5秒
    exit(0); //或$this-&gt;_redirect(&#039;/&#039;);
  }else{
    //第一个子进程code
    //产生孙进程
    if(($gpid = pcntl_fork()) &lt; 0){ ////$gpid即所产生的孙进程id
      //孙进程产生失败
      die(&#039;could not fork&#039;);
    }elseif($gpid &gt; 0){
      //第一个子进程code，即孙进程的父进程
      $status = 0;
      $status = pcntl_wait($status); //阻塞子进程,并返回孙进程的退出状态，用于检查是否正常退出
      if($status ! = 0) file_put_content(&#039;filename&#039;, &#039;孙进程异常退出&#039;);
      //得到父进程id
      //$ppid =  posix_getppid(); //如果$ppid为1则表示其父进程已变为init进程，原父进程已退出
      //得到子进程id：posix_getpid()或getmypid()或是fork返回的变量$pid
      //kill掉子进程
      //posix_kill(getmypid(), SIGTERM);
      exit(0);
    }else{ //即$gpid == 0
      //孙进程code
      //....
      //结束孙进程(即当前进程)，以防止生成僵尸进程
      if(function_exists(&#039;posix_kill&#039;)){
         posix_kill(getmypid(), SIGTERM);
      }else{
         system(&#039;kill -9&#039;. getmypid());
      }
      exit(0);
    }
  }
}
}else{
 // 不支持多进程处理时的代码在这里
}
//.....
?&gt;
</pre>
<p>怎样产生僵尸进程的<br />
一个进程在调用exit命令结束自己的生命的时候，其实它并没有真正的被销毁，而是留下一个称为僵尸进程（Zombie）的数据结构（系统调用exit，它的作用是使进程退出，但也仅仅限于将一个正常的进程变成一个僵尸进程，并不能将其完全销毁）。在Linux进程的状态中，僵尸进程是非常特殊的一种，它已经放弃了几乎所有内存空间，没有任何可执行代码，也不能被调度，仅仅在进程列表中保留一个位置，记载该进程的退出状态等信息供其他进程收集，除此之外，僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸，如果他的父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束，又没有显式忽略该信号，那么它就一直保持僵尸状态，如果这时父进程结束了，那么init进程自动会接手这个子进程，为它收尸，它还是能被清除的。但是如果如果父进程是一个循环，不会结束，那么子进程就会一直保持僵尸状态，这就是为什么系统中有时会有很多的僵尸进程。</p>
<p>任何一个子进程(init除外)在exit()之后，并非马上就消失掉，而是留下一个称为僵尸进程(Zombie)的数据结构，等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后，父进程没有来得及处理，这时用ps命令就能看到子进程的状态是”Z”。如果父进程能及时 处理，可能用ps命令就来不及看到子进程的僵尸状态，但这并不等于子进程不经过僵尸状态。</p>
<p>如果父进程在子进程结束之前退出，则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。</p>
<p>另外，还可以写一个php文件，然后在以后台形式来运行它，例如：</p>
<pre>
&lt;?php
//Action代码
public function createAction(){
    //....
    //将args替换成要传给insertLargeData.php的参数，参数间用空格间隔
    system(&#039;php -f insertLargeData.php &#039; . &#039; args &#039; . &#039;&amp;&#039;);
    $this-&gt;redirect(&#039;/&#039;);
}
?&gt;
</pre>
<p>然后在insertLargeData.php文件中做数据库操作。也可以用cronjob + php的方式实现大数据量的处理。</p>
<p>如果是在终端运行php命令，当终端关闭后，刚刚执行的命令也会被强制关闭，如果你想让其不受终端关闭的影响，可以使用nohup命令实现：</p>
<pre>
&lt;?php
//Action代码
public function createAction(){
    //....
    //将args替换成要传给insertLargeData.php的参数，参数间用空格间隔
    system(&#039;nohup php -f insertLargeData.php &#039; . &#039; args &#039; . &#039;&amp;&#039;);
    $this-&gt;redirect(&#039;/&#039;);
}
?&gt;
</pre>
<p>你还可以使用screen命令代替nohup命令。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hdj.me/php-multithread/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP数据流应用的一个简单例子</title>
		<link>http://www.hdj.me/a-simple-example-of-php-stream</link>
		<comments>http://www.hdj.me/a-simple-example-of-php-stream#comments</comments>
		<pubDate>Wed, 09 May 2012 07:11:02 +0000</pubDate>
		<dc:creator>huangdijia</dc:creator>
				<category><![CDATA[PHP/PHP框架]]></category>
		<category><![CDATA[phpinput]]></category>
		<category><![CDATA[php流]]></category>

		<guid isPermaLink="false">http://www.hdj.me/?p=958</guid>
		<description><![CDATA[&#60;?php $count = 5; start: if($count &#60; 5) echo &#34;You can try {$count} time, &#34;; echo &#34;Put Password: &#34;; $handle = fopen (&#34;php://stdin&#34;,&#34;r&#34;); $line = fgets($handle); if(trim($line) != &#039;123456&#039;){ $count--; if(!$count) goto error; goto start; } goto success; error: echo &#34;Please try after 1 hour!&#34;; goto out; success: echo &#34;Logined!&#34;; out: ?&#62; 执行结果：]]></description>
			<content:encoded><![CDATA[<pre>&lt;?php
$count = 5;
start:
if($count &lt; 5) echo &quot;You can try {$count} time, &quot;;
echo &quot;Put Password: &quot;;
$handle = fopen (&quot;php://stdin&quot;,&quot;r&quot;);
$line = fgets($handle);
if(trim($line) != &#039;123456&#039;){
    $count--;
    if(!$count) goto error;
    goto start;
}
goto success;
error:
    echo &quot;Please try after 1 hour!&quot;;
    goto out;
success:
    echo &quot;Logined!&quot;;
out:
?&gt;</pre>
<p>执行结果：<br />
<a href="http://www.hdj.me/wp-content/uploads/2012/05/QQ截图20120509151125.png"><img src="http://www.hdj.me/wp-content/uploads/2012/05/QQ截图20120509151125.png" alt="" title="QQ截图20120509151125" width="354" height="164" class="alignnone size-full wp-image-960" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.hdj.me/a-simple-example-of-php-stream/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP合并两个数组的两种方式的异同</title>
		<link>http://www.hdj.me/diff-of-array-merge-in-php</link>
		<comments>http://www.hdj.me/diff-of-array-merge-in-php#comments</comments>
		<pubDate>Tue, 08 May 2012 05:06:32 +0000</pubDate>
		<dc:creator>huangdijia</dc:creator>
				<category><![CDATA[PHP/PHP框架]]></category>
		<category><![CDATA[array_merge]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[数组合并]]></category>

		<guid isPermaLink="false">http://www.hdj.me/?p=942</guid>
		<description><![CDATA[对数组的array_merge函数和+运算符比较迷惑，写了个小程序比较下发现了他们的不同。 特别是+运算符，他的意思是，将右边的数组单元（去重复）追加到左边数组的后面。 &#60;?php echo &#34;\r\n第一种情况\r\n&#34;; $a=array(1,2,3,4,5,6); $b=array(7,8,9); $c=array_merge ($a,$b); print_r($c); $c=$a+$b; print_r($c); $c=$b+$a; print_r($c); echo &#34;\r\n第二种情况\r\n&#34;; $a=array(&#039;a&#039;,&#039;b&#039;,&#039;c&#039;,&#039;d&#039;,&#039;e&#039;,&#039;f&#039;); $b=array(&#039;a&#039;,&#039;x&#039;,&#039;y&#039;); $c=array_merge ($a,$b); print_r($c); $c=$a+$b; print_r($c); $c=$b+$a; print_r($c); echo &#34;\r\n第三种情况\r\n&#34;; $a=array( 1=&#62;&#039;a&#039;, 2=&#62;&#039;b&#039;, 3=&#62;&#039;c&#039;, 4=&#62;&#039;d&#039;, 5=&#62;&#039;e&#039;, 6=&#62;&#039;f&#039;); $b=array( 1=&#62;&#039;a&#039;, 7=&#62;&#039;x&#039;, 8=&#62;&#039;y&#039;); $c=array_merge ($a,$b); print_r($c); $c=$a+$b; print_r($c); $c=$b+$a; print_r($c); ?&#62; 结果如下： 第一种情况 Array ( [0] => 1 [1] => [...]]]></description>
			<content:encoded><![CDATA[<p>对数组的array_merge函数和+运算符比较迷惑，写了个小程序比较下发现了他们的不同。<br />
特别是+运算符，他的意思是，将右边的数组单元（去重复）追加到左边数组的后面。<span id="more-942"></span></p>
<pre>
&lt;?php
echo &quot;\r\n第一种情况\r\n&quot;;
$a=array(1,2,3,4,5,6);
$b=array(7,8,9);

$c=array_merge ($a,$b);
print_r($c);
$c=$a+$b;
print_r($c);
$c=$b+$a;
print_r($c);

echo &quot;\r\n第二种情况\r\n&quot;;
$a=array(&#039;a&#039;,&#039;b&#039;,&#039;c&#039;,&#039;d&#039;,&#039;e&#039;,&#039;f&#039;);
$b=array(&#039;a&#039;,&#039;x&#039;,&#039;y&#039;);

$c=array_merge ($a,$b);
print_r($c);
$c=$a+$b;
print_r($c);
$c=$b+$a;
print_r($c);

echo &quot;\r\n第三种情况\r\n&quot;;

$a=array(
 1=&gt;&#039;a&#039;,
 2=&gt;&#039;b&#039;,
 3=&gt;&#039;c&#039;,
 4=&gt;&#039;d&#039;,
 5=&gt;&#039;e&#039;,
 6=&gt;&#039;f&#039;);
$b=array(
 1=&gt;&#039;a&#039;,
 7=&gt;&#039;x&#039;,
 8=&gt;&#039;y&#039;);

$c=array_merge ($a,$b);
print_r($c);
$c=$a+$b;
print_r($c);
$c=$b+$a;
print_r($c);
?&gt;
</pre>
<p>结果如下：</p>
<pre>
第一种情况
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
    [5] => 6
    [6] => 7
    [7] => 8
    [8] => 9
)
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
    [5] => 6
)
Array
(
    [0] => 7
    [1] => 8
    [2] => 9
    [3] => 4
    [4] => 5
    [5] => 6
)

第二种情况
Array
(
    [0] => a
    [1] => b
    [2] => c
    [3] => d
    [4] => e
    [5] => f
    [6] => a
    [7] => x
    [8] => y
)
Array
(
    [0] => a
    [1] => b
    [2] => c
    [3] => d
    [4] => e
    [5] => f
)
Array
(
    [0] => a
    [1] => x
    [2] => y
    [3] => d
    [4] => e
    [5] => f
)

第三种情况
Array
(
    [0] => a
    [1] => b
    [2] => c
    [3] => d
    [4] => e
    [5] => f
    [6] => a
    [7] => x
    [8] => y
)
Array
(
    [1] => a
    [2] => b
    [3] => c
    [4] => d
    [5] => e
    [6] => f
    [7] => x
    [8] => y
)
Array
(
    [1] => a
    [7] => x
    [8] => y
    [2] => b
    [3] => c
    [4] => d
    [5] => e
    [6] => f
) </pre>
]]></content:encoded>
			<wfw:commentRss>http://www.hdj.me/diff-of-array-merge-in-php/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>HTTP请求模型</title>
		<link>http://www.hdj.me/http-request-model</link>
		<comments>http://www.hdj.me/http-request-model#comments</comments>
		<pubDate>Sun, 06 May 2012 15:23:29 +0000</pubDate>
		<dc:creator>huangdijia</dc:creator>
				<category><![CDATA[Web服务器]]></category>

		<guid isPermaLink="false">http://www.hdj.me/?p=936</guid>
		<description><![CDATA[一、连接至Web服务器 一个客户端应用（如Web浏览器）打开到Web服务器的HTTP端口的一个套接字（缺省为80）。 例如： http://www.myweb.com:8080/index.html 在Java中，这将等同于代码： Soceet socket=new Socket("www.myweb.com",8080); InputStream in=socket.getInputStream(); OutputStream out=socket.getOutputStream(); 二、发送HTTP请求 通过连接，客户端写一个ASCII文本请求行，后跟0或多个HTTP头标，一个空行和实现请求的任意数据。 一个请求由四个部分组成：请求行、请求头标、空行和请求数据 1.请求行：请求行由三个标记组成：请求方法、请求URI和HTTP版本，它们用空格分隔。 例如： GET /index.html HTTP/1.1 HTTP规范定义了8种可能的请求方法： GET 检索URI中标识资源的一个简单请求 HEAD 与GET方法相同，服务器只返回状态行和头标，并不返回请求文档 POST 服务器接受被写入客户端输出流中的数据的请求 PUT 服务器保存请求数据作为指定URI新内容的请求 DELETE 服务器删除URI中命名的资源的请求 OPTIONS 关于服务器支持的请求方法信息的请求 TRACE Web服务器反馈Http请求和其头标的请求 CONNECT 已文档化但当前未实现的一个方法，预留做隧道处理 2.请求头标：由关键字/值对组成，每行一对，关键字和值用冒号（:）分隔。 请求头标通知服务器有关于客户端的功能和标识，典型的请求头标有： User-Agent 客户端厂家和版本 Accept 客户端可识别的内容类型列表 Content-Length 附加到请求的数据字节数 3.空行：最后一个请求头标之后是一个空行，发送回车符和退行，通知服务器以下不再有头标。 4.请求数据：使用POST传送数据，最常使用的是Content-Type和Content-Length头标。 三、服务端接受请求并返回HTTP响应 Web服务器解析请求，定位指定资源。服务器将资源副本写至套接字，在此处由客户端读取。 一个响应由四个部分组成；状态行、响应头标、空行、响应数据 1.状态行：状态行由三个标记组成：HTTP版本、响应代码和响应描述。 HTTP版本：向客户端指明其可理解的最高版本。 响应代码：3位的数字代码，指出请求的成功或失败，如果失败则指出原因。 响应描述：为响应代码的可读性解释。 例如： [...]]]></description>
			<content:encoded><![CDATA[<p><strong>一、连接至Web服务器</strong><br />
一个客户端应用（如Web浏览器）打开到Web服务器的HTTP端口的一个套接字（缺省为80）。</p>
<p>例如：</p>
<blockquote><p>http://www.myweb.com:8080/index.html</p></blockquote>
<p>在Java中，这将等同于代码：</p>
<pre>
Soceet socket=new Socket("www.myweb.com",8080);
InputStream in=socket.getInputStream();
OutputStream out=socket.getOutputStream();
</pre>
<p><strong>二、发送HTTP请求</strong><br />
通过连接，客户端写一个ASCII文本请求行，后跟0或多个HTTP头标，一个空行和实现请求的任意数据。<br />
<span id="more-936"></span><br />
一个请求由四个部分组成：请求行、请求头标、空行和请求数据</p>
<p>1.请求行：请求行由三个标记组成：请求方法、请求URI和HTTP版本，它们用空格分隔。<br />
例如：</p>
<blockquote><p>GET /index.html HTTP/1.1</p></blockquote>
<p>HTTP规范定义了8种可能的请求方法：<br />
GET                 检索URI中标识资源的一个简单请求<br />
HEAD               与GET方法相同，服务器只返回状态行和头标，并不返回请求文档<br />
POST                服务器接受被写入客户端输出流中的数据的请求<br />
PUT                 服务器保存请求数据作为指定URI新内容的请求<br />
DELETE            服务器删除URI中命名的资源的请求<br />
OPTIONS          关于服务器支持的请求方法信息的请求<br />
TRACE             Web服务器反馈Http请求和其头标的请求<br />
CONNECT        已文档化但当前未实现的一个方法，预留做隧道处理</p>
<p>2.请求头标：由关键字/值对组成，每行一对，关键字和值用冒号（:）分隔。 请求头标通知服务器有关于客户端的功能和标识，典型的请求头标有：</p>
<p>User-Agent        客户端厂家和版本<br />
Accept            客户端可识别的内容类型列表<br />
Content-Length    附加到请求的数据字节数</p>
<p>3.空行：最后一个请求头标之后是一个空行，发送回车符和退行，通知服务器以下不再有头标。<br />
4.请求数据：使用POST传送数据，最常使用的是Content-Type和Content-Length头标。</p>
<p><strong>三、服务端接受请求并返回HTTP响应</strong><br />
Web服务器解析请求，定位指定资源。服务器将资源副本写至套接字，在此处由客户端读取。</p>
<p>一个响应由四个部分组成；状态行、响应头标、空行、响应数据</p>
<p>1.状态行：状态行由三个标记组成：HTTP版本、响应代码和响应描述。<br />
HTTP版本：向客户端指明其可理解的最高版本。<br />
响应代码：3位的数字代码，指出请求的成功或失败，如果失败则指出原因。<br />
响应描述：为响应代码的可读性解释。<br />
例如：</p>
<blockquote><p>HTTP/1.1 200 OK</p></blockquote>
<p>HTTP响应码：<br />
1xx：信息，请求收到，继续处理<br />
2xx：成功，行为被成功地接受、理解和采纳<br />
3xx：重定向，为了完成请求，必须进一步执行的动作<br />
4xx：客户端错误：<br />
2.响应头标：像请求头标一样，它们指出服务器的功能，标识出响应数据的细节。<br />
3.空行：最后一个响应头标之后是一个空行，发送回车符和退行，表明服务器以下不再有头标。<br />
4.响应数据：HTML文档和图像等，也就是HTML本身。</p>
<p><strong>四、服务器关闭连接，浏览器解析响应</strong><br />
1.浏览器首先解析状态行，查看表明请求是否成功的状态代码。<br />
2.然后解析每一个响应头标，头标告知以下为若干字节的HTML。<br />
3.读取响应数据HTML，根据HTML的语法和语义对其进行格式化，并在浏览器窗口中显示它。<br />
4.一个HTML文档可能包含其它需要被载入的资源引用，浏览器识别这些引用，对其它的资源再进行额外的请求，此过程循环多次。</p>
<p><strong>五、无状态连接</strong><br />
HTTP模型是无状态的，表明在处理一个请求时，Web服务器并不记住来自同一客户端的请求。</p>
<p><strong>六、实例</strong><br />
1.浏览器发出请求</p>
<blockquote><p>GET /index.html HTTP/1.1</p></blockquote>
<p>服务器返回响应:</p>
<pre>
HTTP /1.1 200 OK
Date: Apr 11 2006 15:32:08 GMT
Server: Apache/2.0.46(win32)
Content-Length: 119
Content-Type: text/html

&lt;HTML&gt;
&lt;HEAD&gt;
&lt;LINK REL=&quot;stylesheet&quot; HREF=&quot;index.css&quot;&gt;
&lt;/HEAD&gt;
&lt;BODY&gt;
&lt;IMG SRC=&quot;image/logo.png&quot;&gt;
&lt;/BODY&gt;
&lt;/HTML&gt;
</pre>
<p>2.浏览器发出请求</p>
<pre>
GET /index.css HTTP/1.1
</pre>
<p>服务器返回响应:</p>
<pre>
HTTP /1.1 200 OK
Date: Apr 11 2006 15:32:08 GMT
Server: Apache/2.0.46(win32)
Connection: Keep-alive, close
Content-Length: 70
Content-Type: text/plane

h3{
    font-size:20px;
    font-weight:bold;
    color:#005A9C;
}
</pre>
<p>3.浏览器发出请求</p>
<blockquote><p>GET image/logo.png HTTP/1.1</p></blockquote>
<p>服务器返回响应:</p>
<pre>
HTTP /1.1 200 OK
Date: Apr 11 2006 15:32:08 GMT
Server: Apache/2.0.46(win32)
Connection: Keep-alive, close
Content-Length: 1280
Content-Type: text/plane

{Binary image data follows}
</pre>
<p><strong>附录</strong><br />
1.HTTP规范：Internet工程制定组织（IETF）发布的RFC指定Internet标准，这些RFC被Internet研究发展机构广泛接受。因为它们是标准文档，故一般用正规语言编写，如立法文标一样。</p>
<p>2.RFC：RFC一旦被提出，就被编号且不会再改变，当一个标准被修改时，则给出一个新的RFC。作为标准，RFC在Internet上被广泛采用。</p>
<p>3.HTTP的几个重要RFC：<br />
    RFC1945    HTTP 1.0 描述<br />
    RFC2068    HTTP 1.1 初步描述<br />
    RFC2616    HTTP 1.1 标准<br />
4.资源标识符URI（Uniform Resource Identifter，URI）<br />
HTTP参考</p>
<p><strong>一、HTTP码应码</strong><br />
响应码由三位十进制数字组成，它们出现在由HTTP服务器发送的响应的第一行。</p>
<p>响应码分五种类型，由它们的第一位数字表示：<br />
1.1xx：信息，请求收到，继续处理<br />
2.2xx：成功，行为被成功地接受、理解和采纳<br />
3.3xx：重定向，为了完成请求，必须进一步执行的动作<br />
4.4xx：客户端错误，请求包含语法错误或者请求无法实现<br />
5.5xx：服务器错误，服务器不能实现一种明显无效的请求</p>
<p>下表显示每个响应码及其含义：<br />
100            继续<br />
101            分组交换协<br />
200            OK<br />
201            被创建<br />
202            被采纳<br />
203            非授权信息<br />
204            无内容<br />
205            重置内容<br />
206            部分内容<br />
300            多选项<br />
301            永久地传送<br />
302            找到<br />
303            参见其他<br />
304            未改动<br />
305            使用代理<br />
307            暂时重定向<br />
400            错误请求<br />
401            未授权<br />
402            要求付费<br />
403            禁止<br />
404            未找到<br />
405            不允许的方法<br />
406            不被采纳<br />
407            要求代理授权<br />
408            请求超时<br />
409            冲突<br />
410            过期的<br />
411            要求的长度<br />
412            前提不成立<br />
413            请求实例太大<br />
414            请求URI太大<br />
415            不支持的媒体类型<br />
416            无法满足的请求范围<br />
417            失败的预期<br />
500            内部服务器错误<br />
501            未被使用<br />
502            网关错误<br />
503            不可用的服务<br />
504            网关超时<br />
505            HTTP版本未被支持</p>
<p><strong>二、HTTP头标</strong><br />
头标由主键/值对组成。它们描述客户端或者服务器的属性、被传输的资源以及应该实现连接。</p>
<p>四种不同类型的头标：<br />
1.通用头标：即可用于请求，也可用于响应，是作为一个整体而不是特定资源与事务相关联。<br />
2.请求头标：允许客户端传递关于自身的信息和希望的响应形式。<br />
3.响应头标：服务器和于传递自身信息的响应。<br />
4.实体头标：定义被传送资源的信息。即可用于请求，也可用于响应。</p>
<p>头标格式：</p>
<pre>&lt;name&gt;:&lt;value&gt;&lt;CRLF&gt;</pre>
<p>下表描述在HTTP/1.1中用到的头标<br />
Accept<br />
定义客户端可以处理的媒体类型，按优先级排序；<br />
在一个以逗号为分隔的列表中，可以定义多种类型和使用通配符。例如：Accept: image/jpeg,image/png,*/*</p>
<p>Accept-Charset<br />
定义客户端可以处理的字符集，按优先级排序；<br />
在一个以逗号为分隔的列表中，可以定义多种类型和使用通配符。例如：Accept-Charset: iso-8859-1,*,utf-8</p>
<p>Accept-Encoding<br />
定义客户端可以理解的编码机制。例如：Accept-Encoding:gzip,compress</p>
<p>Accept-Language<br />
定义客户端乐于接受的自然语言列表。例如：Accept-Language: en,de</p>
<p>Accept-Ranges<br />
一个响应头标，它允许服务器指明：将在给定的偏移和长度处，为资源组成部分的接受请求。<br />
该头标的值被理解为请求范围的度量单位。例如Accept-Ranges: bytes或Accept-Ranges: nonea</p>
<p>Age<br />
允许服务器规定自服务器生成该响应以来所经过的时间长度，以秒为单位。<br />
该头标主要用于缓存响应。例如：Age: 30</p>
<p>Allow<br />
一个响应头标，它定义一个由位于请求URI中的次源所支持的HTTP方法列表。例如：Allow: GET,PUT</p>
<p>aUTHORIZATION<br />
一个响应头标，用于定义访问一种资源所必需的授权（域和被编码的用户ID与口令）。<br />
例如：Authorization: Basic YXV0aG9yOnBoaWw=</p>
<p>Cache-Control<br />
一个用于定义缓存指令的通用头标。例如：Cache-Control: max-age=30</p>
<p>Connection<br />
一个用于表明是否保存socket连接为开放的通用头标。例如：Connection: close或Connection: keep-alive</p>
<p>Content-Base<br />
一种定义基本URI的实体头标，为了在实体范围内解析相对URLs。<br />
如果没有定义Content-Base头标解析相对URLs，使用Content-Location URI（存在且绝对）或使用URI请求。<br />
例如：Content-Base: Http://www.myweb.com</p>
<p>Content-Encoding<br />
一种介质类型修饰符，标明一个实体是如何编码的。例如：Content-Encoding: zip<br />
Content-Language<br />
用于指定在输入流中数据的自然语言类型。例如：Content-Language: en<br />
Content-Length<br />
指定包含于请求或响应中数据的字节长度。例如：Content-Length:382</p>
<p>Content-Location<br />
指定包含于请求或响应中的资源定位（URI）。<br />
如果是一绝。对URL它也作为被解析实体的相对URL的出发点。<br />
例如：Content-Location: http://www.myweb.com/news</p>
<p>Content-MD5<br />
实体的一种MD5摘要，用作校验和。<br />
发送方和接受方都计算MD5摘要，接受方将其计算的值与此头标中传递的值进行比较。<br />
例如：Content-MD5: <base64 of 128 MD5 digest></p>
<p>Content-Range<br />
随部分实体一同发送；标明被插入字节的低位与高位字节偏移，也标明此实体的总长度。<br />
例如：Content-Range: 1001-2000/5000</p>
<p>Contern-Type<br />
标明发送或者接收的实体的MIME类型。例如：Content-Type: text/html<br />
Date<br />
发送HTTP消息的日期。例如：Date: Mon,10PR 18:42:51 GMT</p>
<p>ETag<br />
一种实体头标，它向被发送的资源分派一个唯一的标识符。<br />
对于可以使用多种URL请求的资源，ETag可以用于确定实际被发送的资源是否为同一资源。<br />
例如：ETag: “208f-419e-30f8dc99&#8243;</p>
<p>Expires<br />
指定实体的有效期。例如：Expires: Mon,05 Dec 2008 12:00:00 GMT<br />
Form<br />
一种请求头标，给定控制用户代理的人工用户的电子邮件地址。例如：From: webmaster@myweb.com<br />
Host<br />
被请求资源的主机名。对于使用HTTP/1.1的请求而言，此域是强制性的。例如：Host: www.myweb.com</p>
<p>If-Modified-Since<br />
如果包含了GET请求，导致该请求条件性地依赖于资源上次修改日期。<br />
如果出现了此头标，并且自指定日期以来，此资源已被修改，应该反回一个304响应代码。<br />
例如：If-Modified-Since: Mon,10PR 18:42:51 GMT</p>
<p>If-Match<br />
如果包含于一个请求，指定一个或者多个实体标记。只发送其ETag与列表中标记区配的资源。<br />
例如：If-Match: “208f-419e-308dc99&#8243;</p>
<p>If-None-Match<br />
如果包含一个请求，指定一个或者多个实体标记。资源的ETag不与列表中的任何一个条件匹配，操作才执行。<br />
例如：If-None-Match: “208f-419e-308dc99&#8243;</p>
<p>If-Range<br />
指定资源的一个实体标记，客户端已经拥有此资源的一个拷贝。必须与Range头标一同使用。<br />
如果此实体自上次被客户端检索以来，还不曾修改过，那么服务器只发送指定的范围，否则它将发送整个资源。<br />
例如：Range: byte=0-499<CRLF>If-Range:”208f-419e-30f8dc99&#8243;</p>
<p>If-Unmodified-Since<br />
只有自指定的日期以来，被请求的实体还不曾被修改过，才会返回此实体。<br />
例如：If-Unmodified-Since:Mon,10PR 18:42:51 GMT</p>
<p>Last-Modified<br />
指定被请求资源上次被修改的日期和时间。例如：Last-Modified: Mon,10PR 18:42:51 GMT</p>
<p>Location<br />
对于一个已经移动的资源，用于重定向请求者至另一个位置。<br />
与状态编码302（暂时移动）或者301（永久性移动）配合使用。<br />
例如：Location: http://www2.myweb.com/index.jsp</p>
<p>Max-Forwards<br />
一个用于TRACE方法的请求头标，以指定代理或网关的最大数目，该请求通过网关才得以路由。<br />
在通过请求传递之前，代理或网关应该减少此数目。例如：Max-Forwards: 3</p>
<p>Pragma<br />
一个通用头标，它发送实现相关的信息。例如：Pragma: no-cache</p>
<p>Proxy-Authenticate<br />
类似于WWW-Authenticate，便是有意请求只来自请求链（代理）的下一个服务器的认证。<br />
例如：Proxy-Authenticate: Basic realm-admin</p>
<p>Proxy-Proxy-Authorization<br />
类似于授权，但并非有意传递任何比在即时服务器链中更进一步的内容。<br />
例如：Proxy-Proxy-Authorization: Basic YXV0aG9yOnBoaWw=</p>
<p>Public<br />
列表显示服务器所支持的方法集。例如：Public: OPTIONS,MGET,MHEAD,GET,HEAD</p>
<p>Range<br />
指定一种度量单位和一个部分被请求资源的偏移范围。例如：Range: bytes=206-5513</p>
<p>Refener<br />
一种请求头标域，标明产生请求的初始资源。对于HTML表单，它包含此表单的Web页面的地址。<br />
例如：Refener: http://www.myweb.com/news/search.html</p>
<p>Retry-After<br />
一种响应头标域，由服务器与状态编码503（无法提供服务）配合发送，以标明再次请求之前应该等待多长时间。<br />
此时间即可以是一种日期，也可以是一种秒单位。例如：Retry-After: 18</p>
<p>Server<br />
一种标明Web服务器软件及其版本号的头标。例如：Server: Apache/2.0.46(Win32)</p>
<p>Transfer-Encoding<br />
一种通用头标，标明对应被接受方反向的消息体实施变换的类型。例如：Transfer-Encoding: chunked</p>
<p>Upgrade<br />
允许服务器指定一种新的协议或者新的协议版本，与响应编码101（切换协议）配合使用。<br />
例如：Upgrade: HTTP/2.0</p>
<p>User-Agent<br />
定义用于产生请求的软件类型（典型的如Web浏览器）。<br />
例如：User-Agent: Mozilla/4.0(compatible; MSIE 5.5; Windows NT; DigExt)</p>
<p>Vary<br />
一个响应头标，用于表示使用服务器驱动的协商从可用的响应表示中选择响应实体。例如：Vary: *</p>
<p>Via<br />
一个包含所有中间主机和协议的通用头标，用于满足请求。例如：Via: 1.0 fred.com, 1.1 wilma.com</p>
<p>Warning<br />
用于提供关于响应状态补充信息的响应头标。例如：Warning: 99 www.myweb.com Piano needs tuning</p>
<p>www-Authenticate<br />
一个提示用户代理提供用户名和口令的响应头标，与状态编码401（未授权）配合使用。响应一个授权头标。<br />
例如：www-Authenticate: Basic realm=zxm.mgmt</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hdj.me/http-request-model/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP的autoload机制的实现解析</title>
		<link>http://www.hdj.me/autoload-of-php</link>
		<comments>http://www.hdj.me/autoload-of-php#comments</comments>
		<pubDate>Fri, 04 May 2012 15:54:20 +0000</pubDate>
		<dc:creator>huangdijia</dc:creator>
				<category><![CDATA[PHP/PHP框架]]></category>
		<category><![CDATA[Web服务器]]></category>
		<category><![CDATA[autoload]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://www.hdj.me/?p=931</guid>
		<description><![CDATA[一、autoload机制概述 在使用PHP的OO模式开发系统时，通常大家习惯上将每个类的实现都存放在一个单独的文件里，这样会很容易实现对类进行复用，同时将来维护时也很便利。这也是OO设计的基本思想之一。在PHP5之前，如果需要使用一个类，只需要直接使用include/require将其包含进来即可。 下面是一个实际的例子： /* Person.class.php */ &#60;?php class Person { var $name, $age; function __construct ($name, $age) { $this-&#62;name = $name; $this-&#62;age = $age; } } ?&#62; /* no_autoload.php */ &#60;?php require_once (&#34;Person.class.php&#34;); $person = new Person(&#34;Altair&#34;, 6); var_dump ($person); 在这个例子中，no-autoload.php文件需要使用Person类，它使用了require_once将其包含，然后就可以直接使用Person类来实例化一个对象。 但随着项目规模的不断扩大，使用这种方式会带来一些隐含的问题：如果一个PHP文件需要使用很多其它类，那么就需要很多的require/include语句，这样有可能会造成遗漏或者包含进不必要的类文件。如果大量的文件都需要使用其它的类，那么要保证每个文件都包含正确的类文件肯定是一个噩梦。 PHP5为这个问题提供了一个解决方案，这就是类的自动装载(autoload)机制。autoload机制可以使得PHP程序有可能在使用类时才自动包含类文件，而不是一开始就将所有的类文件include进来，这种机制也称为lazy loading。 下面是使用autoload机制加载Person类的例子： /* autoload.php */ &#60;?php function __autoload($classname) { require_once ($classname . [...]]]></description>
			<content:encoded><![CDATA[<p><strong>一、autoload机制概述</strong><br />
在使用PHP的OO模式开发系统时，通常大家习惯上将每个类的实现都存放在一个单独的文件里，这样会很容易实现对类进行复用，同时将来维护时也很便利。这也是OO设计的基本思想之一。在PHP5之前，如果需要使用一个类，只需要直接使用include/require将其包含进来即可。<span id="more-931"></span><br />
下面是一个实际的例子：</p>
<pre>
/* Person.class.php */
&lt;?php
class Person {
  var $name, $age;

  function __construct ($name, $age)
  {
   $this-&gt;name = $name;
   $this-&gt;age = $age;
  }
}
?&gt;
</pre>
<pre>
/* no_autoload.php */
&lt;?php
require_once (&quot;Person.class.php&quot;);

$person = new Person(&quot;Altair&quot;, 6);
var_dump ($person);
</pre>
<p>在这个例子中，no-autoload.php文件需要使用Person类，它使用了require_once将其包含，然后就可以直接使用Person类来实例化一个对象。</p>
<p>但随着项目规模的不断扩大，使用这种方式会带来一些隐含的问题：如果一个PHP文件需要使用很多其它类，那么就需要很多的require/include语句，这样有可能会造成遗漏或者包含进不必要的类文件。如果大量的文件都需要使用其它的类，那么要保证每个文件都包含正确的类文件肯定是一个噩梦。</p>
<p>PHP5为这个问题提供了一个解决方案，这就是类的自动装载(autoload)机制。autoload机制可以使得PHP程序有可能在使用类时才自动包含类文件，而不是一开始就将所有的类文件include进来，这种机制也称为lazy loading。</p>
<p>下面是使用autoload机制加载Person类的例子：</p>
<pre>
/* autoload.php */
&lt;?php
function __autoload($classname) {
  require_once ($classname . &quot;class.php&quot;);
}

$person = new Person(&quot;Altair&quot;, 6);
var_dump ($person);
?&gt;</pre>
<p>通常PHP5在使用一个类时，如果发现这个类没有加载，就会自动运行__autoload()函数，在这个函数中我们可以加载需要使用的类。在我们这个简单的例子中，我们直接将类名加上扩展名”.class.php”构成了类文件名，然后使用require_once将其加载。从这个例子中，我们可以看出autoload至少要做三件事情，第一件事是根据类名确定类文件名，第二件事是确定类文件所在的磁盘路径(在我们的例子是最简单的情况，类与调用它们的PHP程序文件在同一个文件夹下)，第三件事是将类从磁盘文件中加载到系统中。第三步最简单，只需要使用include/require即可。要实现第一步，第二步的功能，必须在开发时约定类名与磁盘文件的映射方法，只有这样我们才能根据类名找到它对应的磁盘文件。</p>
<p>因此，当有大量的类文件要包含的时候，我们只要确定相应的规则，然后在__autoload()函数中，将类名与实际的磁盘文件对应起来，就可以实现lazy loading的效果。从这里我们也可以看出__autoload()函数的实现中最重要的是类名与实际的磁盘文件映射规则的实现。</p>
<p>但现在问题来了，如果在一个系统的实现中，如果需要使用很多其它的类库，这些类库可能是由不同的开发人员编写的，其类名与实际的磁盘文件的映射规则不尽相同。这时如果要实现类库文件的自动加载，就必须在__autoload()函数中将所有的映射规则全部实现，这样的话__autoload()函数有可能会非常复杂，甚至无法实现。最后可能会导致__autoload()函数十分臃肿，这时即便能够实现，也会给将来的维护和系统效率带来很大的负面影响。在这种情况下，难道就没有更简单清晰的解决办法了吧？答案当然是：NO! 在看进一步的解决方法之前，我们先来看一下PHP中的autoload机制是如何实现的。</p>
<p><strong>二、PHP的autoload机制的实现</strong><br />
我们知道，PHP文件的执行分为两个独立的过程，第一步是将PHP文件编译成普通称之为OPCODE的字节码序列（实际上是编译成一个叫做zend_op_array的字节数组），第二步是由一个虚拟机来执行这些OPCODE。PHP的所有行为都是由这些OPCODE来实现的。因此，为了研究PHP中autoload的实现机制，我们将autoload.php文件编译成opcode，然后根据这些OPCODE来研究PHP在这过程中都做了些什么：</p>
<pre>
/* autoload.php 编译后的OPCODE列表，是使用作者开发的OPDUMP工具
     * 生成的结果，可以到网站 http://www.phpinternals.com/ 下载该软件。
     */
    1: &lt;?php
    2:  // require_once (&quot;Person.php&quot;);
    3:
    4:  function __autoload ($classname) {
            0  NOP
            0  RECV                1
    5:   if (!class_exists($classname)) {
            1  SEND_VAR            !0
            2  DO_FCALL            &#039;class_exists&#039; [extval:1]
            3  BOOL_NOT            $0 =&gt;RES[~1]
            4  JMPZ                ~1, -&gt;8
    6:    require_once ($classname. &quot;.class.php&quot;);
            5  CONCAT              !0, &#039;.class.php&#039; =&gt;RES[~2]
            6  INCLUDE_OR_EVAL     ~2, REQUIRE_ONCE
    7:   }
            7  JMP                 -&gt;8
    8:  }
            8  RETURN              null
    9:
   10:  $p = new Person(&#039;Fred&#039;, 35);
            1  FETCH_CLASS         &#039;Person&#039; =&gt;RES[:0]
            2  NEW                 :0 =&gt;RES[$1]
            3  SEND_VAL            &#039;Fred&#039;
            4  SEND_VAL            35
            5  DO_FCALL_BY_NAME     [extval:2]
            6  ASSIGN              !0, $1
   11:
   12:  var_dump ($p);
            7  SEND_VAR            !0
            8  DO_FCALL            &#039;var_dump&#039; [extval:1]
   13: ?&gt;
</pre>
<p>在autoload.php的第10行代码中我们需要为类Person实例化一个对象。因此autoload机制一定会在该行编译后的opcode中有所体现。从上面的第10行代码生成的OPCODE中我们知道，在实例化对象Person时，首先要执行FETCH_CLASS指令。我们就从PHP对FETCH_CLASS指令的处理过程开始我们的探索之旅。</p>
<p>通过查阅PHP的源代码(我使用的是PHP 5.3alpha2版本)可以发现如下的调用序列：</p>
<pre>
ZEND_VM_HANDLER(109, ZEND_FETCH_CLASS, ...) (zend_vm_def.h 1864行)
=> zend_fetch_class (zend_execute_API.c 1434行)
=>zend_lookup_class_ex (zend_execute_API.c 964行)
=> zend_call_function(&#038;fcall_info, &#038;fcall_cache) (zend_execute_API.c 1040行)
</pre>
<p>在最后一步的调用之前，我们先看一下调用时的关键参数：</p>
<pre>
/* 设置autoload_function变量值为"__autoload" */
fcall_info.function_name = &#038;autoload_function;  // Ooops, 终于发现"__autoload"了
...
fcall_cache.function_handler = EG(autoload_func); // autoload_func !
</pre>
<p>zend_call_function是Zend Engine中最重要的函数之一，其主要功能是执行用户在PHP程序中自定义的函数或者PHP本身的库函数。zend_call_function有两个重要的指针形参数fcall_info, fcall_cache，它们分别指向两个重要的结构，一个是zend_fcall_info, 另一个是zend_fcall_info_cache。zend_call_function主要工作流程如下：如果fcall_cache.function_handler指针为NULL，则尝试查找函数名为fcall_info.function_name的函数，如果存在的话，则执行之；如果fcall_cache.function_handler不为NULL，则直接执行fcall_cache.function_handler指向的函数。</p>
<p>现在我们清楚了，PHP在实例化一个对象时（实际上在实现接口，使用类常数或类中的静态变量，调用类中的静态方法时都会如此），首先会在系统中查找该类（或接口）是否存在，如果不存在的话就尝试使用autoload机制来加载该类。而autoload机制的主要执行过程为：</p>
<p>(1) 检查执行器全局变量函数指针autoload_func是否为NULL。<br />
(2) 如果autoload_func==NULL, 则查找系统中是否定义有__autoload()函数，如果没有，则报告错误并退出。<br />
(3) 如果定义了__autoload()函数，则执行__autoload()尝试加载类，并返回加载结果。<br />
(4) 如果autoload_func不为NULL，则直接执行autoload_func指针指向的函数用来加载类。注意此时并不检查__autoload()函数是否定义。<br />
真相终于大白，PHP提供了两种方法来实现自动装载机制，一种我们前面已经提到过，是使用用户定义的__autoload()函数，这通常在PHP源程序中来实现；另外一种就是设计一个函数，将autoload_func指针指向它，这通常使用C语言在PHP扩展中实现。如果既实现了__autoload()函数，又实现了autoload_func(将autoload_func指向某一PHP函数)，那么只执行autoload_func函数。</p>
<p><strong>三、SPL autoload机制的实现</strong><br />
SPL是Standard PHP Library(标准PHP库)的缩写。它是PHP5引入的一个扩展库，其主要功能包括autoload机制的实现及包括各种Iterator接口或类。SPL autoload机制的实现是通过将函数指针autoload_func指向自己实现的具有自动装载功能的函数来实现的。SPL有两个不同的函数spl_autoload, spl_autoload_call，通过将autoload_func指向这两个不同的函数地址来实现不同的自动加载机制。</p>
<p>spl_autoload是SPL实现的默认的自动加载函数，它的功能比较简单。它可以接收两个参数，第一个参数是$class_name，表示类名，第二个参数$file_extensions是可选的，表示类文件的扩展名，可以在$file_extensions中指定多个扩展名，护展名之间用分号隔开即可；如果不指定的话，它将使用默认的扩展名.inc或.php。spl_autoload首先将$class_name变为小写，然后在所有的include path中搜索$class_name.inc或$class_name.php文件(如果不指定$file_extensions参数的话)，如果找到，就加载该类文件。你可以手动使用spl_autoload(“Person”, “.class.php”)来加载Person类。实际上，它跟require/include差不多，不同的它可以指定多个扩展名。</p>
<p>怎样让spl_autoload自动起作用呢，也就是将autoload_func指向spl_autoload？答案是使用spl_autoload_register函数。在PHP脚本中第一次调用spl_autoload_register()时不使用任何参数，就可以将autoload_func指向spl_autoload。</p>
<p>通过上面的说明我们知道，spl_autoload的功能比较简单，而且它是在SPL扩展中实现的，我们无法扩充它的功能。如果想实现自己的更灵活的自动加载机制怎么办呢？这时，spl_autoload_call函数闪亮登场了。</p>
<p>我们先看一下spl_autoload_call的实现有何奇妙之处。在SPL模块内部，有一个全局变量autoload_functions，它本质上是一个HashTable，不过我们可以将其简单的看作一个链表，链表中的每一个元素都是一个函数指针,指向一个具有自动加载类功能的函数。spl_autoload_call本身的实现很简单，只是简单的按顺序执行这个链表中每个函数，在每个函数执行完成后都判断一次需要的类是否已经加载，如果加载成功就直接返回，不再继续执行链表中的其它函数。如果这个链表中所有的函数都执行完成后类还没有加载，spl_autoload_call就直接退出，并不向用户报告错误。因此，使用了autoload机制，并不能保证类就一定能正确的自动加载，关键还是要看你的自动加载函数如何实现。</p>
<p>那么自动加载函数链表autoload_functions是谁来维护呢？就是前面提到的spl_autoload_register函数。它可以将用户定义的自动加载函数注册到这个链表中，并将autoload_func函数指针指向spl_autoload_call函数（注意有一种情况例外，具体是哪种情况留给大家思考）。我们也可以通过spl_autoload_unregister函数将已经注册的函数从autoload_functions链表中删除。</p>
<p>上节说过，当autoload_func指针非空时，就不会自动执行__autoload()函数了，现在autoload_func已经指向了spl_autoload_call，如果我们还想让__autoload()函数起作用应该怎么办呢？当然还是使用spl_autoload_register(__autoload)调用将它注册到autoload_functions链表中。</p>
<p>现在回到第一节最后的问题，我们有了解决方案：根据每个类库不同的命名机制实现各自的自动加载函数，然后使用spl_autoload_register分别将其注册到SPL自动加载函数队列中就可了。这样我们就不用维护一个非常复杂的__autoload函数了。</p>
<p><strong>四、autoload效率问题及对策</strong><br />
使用autoload机制时，很多人的第一反应就是使用autoload会降低系统效率，甚至有人干脆提议为了效率不要使用autoload。在我们了解了autoload实现的原理后，我们知道autoload机制本身并不是影响系统效率的原因，甚至它还有可能提高系统效率，因为它不会将不需要的类加载到系统中。</p>
<p>那么为什么很多人都有一个使用autoload会降低系统效率的印象呢？实际上，影响autoload机制效率本身恰恰是用户设计的自动加载函数。如果它不能高效的将类名与实际的磁盘文件(注意，这里指实际的磁盘文件，而不仅仅是文件名)对应起来，系统将不得不做大量的文件是否存在(需要在每个include path中包含的路径中去寻找)的判断，而判断文件是否存在需要做磁盘I/O操作，众所周知磁盘I/O操作的效率很低，因此这才是使得autoload机制效率降低的罪魁祸首!</p>
<p>因此，我们在系统设计时，需要定义一套清晰的将类名与实际磁盘文件映射的机制。这个规则越简单越明确，autoload机制的效率就越高。autoload机制并不是天然的效率低下，只有滥用autoload，设计不好的自动装载函数才会导致其效率的降低。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hdj.me/autoload-of-php/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>php垃圾收集机制</title>
		<link>http://www.hdj.me/garbage-collector-of-php</link>
		<comments>http://www.hdj.me/garbage-collector-of-php#comments</comments>
		<pubDate>Fri, 04 May 2012 13:43:11 +0000</pubDate>
		<dc:creator>huangdijia</dc:creator>
				<category><![CDATA[PHP/PHP框架]]></category>
		<category><![CDATA[Web服务器]]></category>
		<category><![CDATA[gc]]></category>
		<category><![CDATA[php5.2垃圾回收]]></category>
		<category><![CDATA[php5.3垃圾回收]]></category>
		<category><![CDATA[phpgc]]></category>
		<category><![CDATA[Reference Counting]]></category>
		<category><![CDATA[scope]]></category>

		<guid isPermaLink="false">http://www.hdj.me/?p=928</guid>
		<description><![CDATA[前言 PHP是一门托管型语言，在PHP编程中程序员不需要手工处理内存资源的分配与释放（使用C编写PHP或Zend扩展除外），这就意味着PHP本身实现了垃圾回收机制（Garbage Collection）。现在如果去PHP官方网站（php.net）可以看到，目前PHP5的两个分支版本PHP5.2和PHP5.3是分别更新的，这是因为许多项目仍然使用5.2版本的PHP，而5.3版本对5.2并不是完全兼容。PHP5.3在PHP5.2的基础上做了诸多改进，其中垃圾回收算法就属于一个比较大的改变。本文将分别讨论PHP5.2和PHP5.3的垃圾回收机制，并讨论这种演化和改进对于程序员编写PHP的影响以及要注意的问题。 PHP变量及关联内存对象的内部表示 垃圾回收说到底是对变量及其所关联内存对象的操作，所以在讨论PHP的垃圾回收机制之前，先简要介绍PHP中变量及其内存对象的内部表示（其C源代码中的表示）。 PHP官方文档中将PHP中的变量划分为两类：标量类型和复杂类型。标量类型包括布尔型、整型、浮点型和字符串；复杂类型包括数组、对象和资源；还有一个NULL比较特殊，它不划分为任何类型，而是单独成为一类。 所有这些类型，在PHP内部统一用一个叫做zval的结构表示，在PHP源代码中这个结构名称为“_zval_struct”。zval的具体定义在PHP源代码的“Zend/zend.h”文件中，下面是相关代码的摘录。 typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { char *val; int len; } str; HashTable *ht; /* hash table value */ zend_object_value obj; } zvalue_value; struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uint [...]]]></description>
			<content:encoded><![CDATA[<h1>前言</h1>
<p>PHP是一门托管型语言，在PHP编程中程序员不需要手工处理内存资源的分配与释放（使用C编写PHP或Zend扩展除外），这就意味着PHP本身实现了垃圾回收机制（Garbage Collection）。现在如果去PHP官方网站（php.net）可以看到，目前PHP5的两个分支版本PHP5.2和PHP5.3是分别更新的，这是因为许多项目仍然使用5.2版本的PHP，而5.3版本对5.2并不是完全兼容。PHP5.3在PHP5.2的基础上做了诸多改进，其中垃圾回收算法就属于一个比较大的改变。本文将分别讨论PHP5.2和PHP5.3的垃圾回收机制，并讨论这种演化和改进对于程序员编写PHP的影响以及要注意的问题。<span id="more-928"></span></p>
<h1>PHP变量及关联内存对象的内部表示</h1>
<p>垃圾回收说到底是对变量及其所关联内存对象的操作，所以在讨论PHP的垃圾回收机制之前，先简要介绍PHP中变量及其内存对象的内部表示（其C源代码中的表示）。</p>
<p>PHP官方文档中将PHP中的变量划分为两类：标量类型和复杂类型。标量类型包括布尔型、整型、浮点型和字符串；复杂类型包括数组、对象和资源；还有一个NULL比较特殊，它不划分为任何类型，而是单独成为一类。</p>
<p>所有这些类型，在PHP内部统一用一个叫做zval的结构表示，在PHP源代码中这个结构名称为“_zval_struct”。zval的具体定义在PHP源代码的“Zend/zend.h”文件中，下面是相关代码的摘录。</p>
<pre>
typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
} zvalue_value;

struct _zval_struct {
    /* Variable information */
    zvalue_value value;     /* value */
    zend_uint refcount__gc;
    zend_uchar type;    /* active type */
    zend_uchar is_ref__gc;
};
</pre>
<p>其中联合体“_zvalue_value”用于表示PHP中所有变量的值，这里之所以使用union，是因为一个zval在一个时刻只能表示一种类型的变量。可以看到_zvalue_value中只有5个字段，但是PHP中算上NULL有8种数据类型，那么PHP内部是如何用5个字段表示8种类型呢？这算是PHP设计比较巧妙的一个地方，它通过复用字段达到了减少字段的目的。例如，在PHP内部布尔型、整型及资源（只要存储资源的标识符即可）都是通过lval字段存储的；dval用于存储浮点型；str存储字符串；ht存储数组（注意PHP中的数组其实是哈希表）；而obj存储对象类型；如果所有字段全部置为0或NULL则表示PHP中的NULL，这样就达到了用5个字段存储8种类型的值。</p>
<p>而当前zval中的value（value的类型即是_zvalue_value）到底表示那种类型，则由“_zval_struct”中的type确定。_zval_struct即是zval在C语言中的具体实现，每个zval表示一个变量的内存对象。除了value和type，可以看到_zval_struct中还有两个字段refcount__gc和is_ref__gc，从其后缀就可以断定这两个家伙与垃圾回收有关。没错，PHP的垃圾回收全靠这俩字段了。其中refcount__gc表示当前有几个变量引用此zval，而is_ref__gc表示当前zval是否被按引用引用，这话听起来很拗口，这和PHP中zval的“Write-On-Copy”机制有关，由于这个话题不是本文重点，因此这里不再详述，读者只需记住refcount__gc这个字段的作用即可。</p>
<h1>PHP5.2中的垃圾回收算法——Reference Counting</h1>
<p>PHP5.2中使用的内存回收算法是大名鼎鼎的Reference Counting，这个算法中文翻译叫做“引用计数”，其思想非常直观和简洁：为每个内存对象分配一个计数器，当一个内存对象建立时计数器初始化为1（因此此时总是有一个变量引用此对象），以后每有一个新变量引用此内存对象，则计数器加1，而每当减少一个引用此内存对象的变量则计数器减1，当垃圾回收机制运作的时候，将所有计数器为0的内存对象销毁并回收其占用的内存。而PHP中内存对象就是zval，而计数器就是refcount__gc。</p>
<p>例如下面一段PHP代码演示了PHP5.2计数器的工作原理（计数器值通过xdebug得到）：</p>
<pre>
&lt;?ph
$val1 = 100; //zval(val1).refcount_gc = 1;
$val2 = $val1; //zval(val1).refcount_gc = 2,zval(val2).refcount_gc = 2(因为是Write on copy，当前val2与val1共同引用一个zval)
$val2 = 200; //zval(val1).refcount_gc = 1,zval(val2).refcount_gc = 1(此处val2新建了一个zval)
unset($val1); //zval(val1).refcount_gc = 0($val1引用的zval再也不可用，会被GC回收
?&gt;</pre>
<p>Reference Counting简单直观，实现方便，但却存在一个致命的缺陷，就是容易造成内存泄露。很多朋友可能已经意识到了，如果存在循环引用，那么Reference Counting就可能导致内存泄露。例如下面的代码：</p>
<pre>
&lt;?php
$a = array();
$a[] = &amp; $a;
unset($a);
?&gt;</pre>
<p>这段代码首先建立了数组a，然后让a的第一个元素按引用指向a，这时a的zval的refcount就变为2，然后我们销毁变量a，此时a最初指向的zval的refcount为1，但是我们再也没有办法对其进行操作，因为其形成了一个循环自引用，如下图所示：</p>
<p><a href="http://www.hdj.me/wp-content/uploads/2012/05/201102261654241747.png"><img src="http://www.hdj.me/wp-content/uploads/2012/05/201102261654241747.png" alt="" title="201102261654241747" width="357" height="300" class="alignnone size-full wp-image-951" /></a></p>
<p>其中灰色部分表示已经不复存在。由于a之前指向的zval的refcount为1（被其HashTable的第一个元素引用），这个zval就不会被GC销毁，这部分内存就泄露了。</p>
<p>这里特别要指出的是，PHP是通过符号表（Symbol Table）存储变量符号的，全局有一个符号表，而每个复杂类型如数组或对象有自己的符号表，因此上面代码中，a和a[0]是两个符号，但是a储存在全局符号表中，而a[0]储存在数组本身的符号表中，且这里a和a[0]引用同一个zval（当然符号a后来被销毁了）。希望读者朋友注意分清符号（Symbol）的zval的关系。</p>
<p>在PHP只用于做动态页面脚本时，这种泄露也许不是很要紧，因为动态页面脚本的生命周期很短，PHP会保证当脚本执行完毕后，释放其所有资源。但是PHP发展到目前已经不仅仅用作动态页面脚本这么简单，如果将PHP用在生命周期较长的场景中，例如自动化测试脚本或deamon进程，那么经过多次循环后积累下来的内存泄露可能就会很严重。这并不是我在耸人听闻，我曾经实习过的一个公司就通过PHP写的deamon进程来与数据存储服务器交互。</p>
<p>由于Reference Counting的这个缺陷，PHP5.3改进了垃圾回收算法。</p>
<h1>PHP5.3中的垃圾回收算法——Concurrent Cycle Collection in Reference Counted Systems</h1>
<p>PHP5.3的垃圾回收算法仍然以引用计数为基础，但是不再是使用简单计数作为回收准则，而是使用了一种同步回收算法，这个算法由IBM的工程师在论文Concurrent Cycle Collection in Reference Counted Systems中提出。</p>
<p>这个算法可谓相当复杂，从论文29页的数量我想大家也能看出来，所以我不打算（也没有能力）完整论述此算法，有兴趣的朋友可以阅读上面的提到的论文（强烈推荐，这篇论文非常精彩）。</p>
<p>我在这里，只能大体描述一下此算法的基本思想。</p>
<p>首先PHP会分配一个固定大小的“根缓冲区”，这个缓冲区用于存放固定数量的zval，这个数量默认是10,000，如果需要修改则需要修改源代码Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES然后重新编译。</p>
<p>由上文我们可以知道，一个zval如果有引用，要么被全局符号表中的符号引用，要么被其它表示复杂类型的zval中的符号引用。因此在zval中存在一些可能根（root）。这里我们暂且不讨论PHP是如何发现这些可能根的，这是个很复杂的问题，总之PHP有办法发现这些可能根zval并将它们投入根缓冲区。</p>
<p>当根缓冲区满额时，PHP就会执行垃圾回收，此回收算法如下：<br />
1、对每个根缓冲区中的根zval按照深度优先遍历算法遍历所有能遍历到的zval，并将每个zval的refcount减1，同时为了避免对同一zval多次减1（因为可能不同的根能遍历到同一个zval），每次对某个zval减1后就对其标记为“已减”。<br />
2、再次对每个缓冲区中的根zval深度优先遍历，如果某个zval的refcount不为0，则对其加1，否则保持其为0。<br />
3、清空根缓冲区中的所有根（注意是把这些zval从缓冲区中清除而不是销毁它们），然后销毁所有refcount为0的zval，并收回其内存。</p>
<p>如果不能完全理解也没有关系，只需记住PHP5.3的垃圾回收算法有以下几点特性：<br />
1、并不是每次refcount减少时都进入回收周期，只有根缓冲区满额后在开始垃圾回收。<br />
2、可以解决循环引用问题。<br />
3、可以总将内存泄露保持在一个阈值以下。</p>
<h1>PHP5.2与PHP5.3垃圾回收算法的性能比较</h1>
<p>由于我目前条件所限，我就不重新设计试验了，而是直接引用PHP Manual中的实验，关于两者的性能比较请参考PHP Manual中的相关章节：http://www.php.net/manual/en/features.gc.performance-considerations.php。</p>
<p>首先是内存泄露试验，下面直接引用PHP Manual中的实验代码和试验结果图：</p>
<pre>
&lt;?php
class Foo
{
    public $var = &#039;3.1415962654&#039;;
}

$baseMemory = memory_get_usage();

for ( $i = 0; $i &lt;= 100000; $i++ )
{
    $a = new Foo;
    $a-&gt;self = $a;
    if ( $i % 500 === 0 )
    {
        echo sprintf( &#039;%8d: &#039;, $i ), memory_get_usage() - $baseMemory, &quot;\n&quot;;
    }
}
?&gt;</pre>
<p><a href="http://www.hdj.me/wp-content/uploads/2012/05/12f37b1c6963c1c5c18f30495416a197-gc-benchmark.png"><img src="http://www.hdj.me/wp-content/uploads/2012/05/12f37b1c6963c1c5c18f30495416a197-gc-benchmark.png" alt="" title="12f37b1c6963c1c5c18f30495416a197-gc-benchmark" width="450" class="alignnone size-full wp-image-952" /></a></p>
<p>可以看到在可能引发累积性内存泄露的场景下，PHP5.2发生持续累积性内存泄露，而PHP5.3则总能将内存泄露控制在一个阈值以下（与根缓冲区大小有关）。</p>
<p>另外是关于性能方面的对比：</p>
<pre>
&lt;?php
class Foo
{
    public $var = &#039;3.1415962654&#039;;
}

for ( $i = 0; $i &lt;= 1000000; $i++ )
{
    $a = new Foo;
    $a-&gt;self = $a;
}

echo memory_get_peak_usage(), &quot;\n&quot;;
?&gt;</pre>
<p>这个脚本执行1000000次循环，使得延迟时间足够进行对比。<br />
然后使用CLI方式分别在打开内存回收和关闭内存回收的的情况下运行此脚本：</p>
<pre>
time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php
# and
time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php
</pre>
<p>在我的机器环境下，运行时间分别为6.4s和7.2s，可以看到PHP5.3的垃圾回收机制会慢一些，但是影响并不大。</p>
<h1>与垃圾回收算法相关的PHP配置</h1>
<p>可以通过修改php.ini中的zend.enable_gc来打开或关闭PHP的垃圾回收机制，也可以通过调用gc_enable()或gc_disable()打开或关闭PHP的垃圾回收机制。在PHP5.3中即使关闭了垃圾回收机制，PHP仍然会记录可能根到根缓冲区，只是当根缓冲区满额时，PHP不会自动运行垃圾回收，当然，任何时候您都可以通过手工调用gc_collect_cycles()函数强制执行内存回收。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hdj.me/garbage-collector-of-php/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP优于Node.js的五大理由</title>
		<link>http://www.hdj.me/php-better-then-nodejs</link>
		<comments>http://www.hdj.me/php-better-then-nodejs#comments</comments>
		<pubDate>Fri, 27 Apr 2012 13:18:28 +0000</pubDate>
		<dc:creator>huangdijia</dc:creator>
				<category><![CDATA[PHP/PHP框架]]></category>
		<category><![CDATA[Web服务器]]></category>
		<category><![CDATA[lnmp]]></category>
		<category><![CDATA[mnmp]]></category>
		<category><![CDATA[nodejs]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[wamp]]></category>

		<guid isPermaLink="false">http://www.hdj.me/?p=922</guid>
		<description><![CDATA[PHP是一款服务器端的脚本语言，主要用于动态网页开发，是目前最流行的开发语言之一。Node是一款用来编写高性能网络服务器的JavaScript工具包。作者Jani Hartikainen发表了一篇博文，文中将两者进行对比，列举了PHP优于Node.js的五大理由。一起来看下。 1. 容易托管 大多数Web托管服务器提供商能为PHP提供托管，而对于Node，你需要找一个更专业的托管服务提供商。通常情况下，需要通过shell访问来设置应用程序，并且大部分托管提供商不包含这些，即便是包含此功能的，其他的包要相对的廉价些。 2. PHP在服务器上安装更方便、简易 PHP可轻易的安装WAMP，LAMP或者MAMP，将代码部署到一个Web虚拟主机，只需要拖放文件，就可大功告成。 虽然，Node本身并不难安装，但是仍需要具备更多的专业知识才能将它设置好，为了能够在服务器上安装，你通常需要了解一些Linux系统管理员方面的知识，以便你在安装过程中当系统奔溃时确保node能够很好的运行。 3. 如果PHP代码损坏，不会拖垮整个服务器 PHP代码只运行在自己的进程范围中，当某个请求显示错误时，它只对特定的请求产生影响。而在Node环境中，所有的请求均在单一的进程服务器中，当某个请求导致未知错误时，整个服务器都会受到影响。 4. PHP进程短暂 在PHP中每个进程对请求持续的时间很短暂，这就意味着你不必为资源配置和内存而担忧。而Node在进程过程中需要运行很长一段时间，你需要小心并妥善管理好内存。比如，如果你忘记从全局数据中删除条目，这会轻易的导致你将内存泄露。 5．更大的标准库 PHP的标准库比Node的标准库要大的多。 结束语： 当然，并不是说PHP在各个方面都优于Node，在某些事情上，Node还是很不错的（比如，信息实时处理方面），Node是一个有趣的产品，它有一个优雅的架构。尽管Node.js 不是银弹，但它仍然是颗子弹。 开发者们对编程语言的喜好总是各有千秋，每一门语言都有它存在的价值，笔者认为选择自己最适合的才是最重要的。除了以上这些，PHP还在哪些方面优于Node呢？欢迎您在评论中列出。]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.php.net/" target="_blank">PHP</a>是一款服务器端的脚本语言，主要用于动态网页开发，是目前最流行的开发语言之一。Node是一款用来编写高性能网络服务器的JavaScript工具包。作者Jani Hartikainen发表了一篇博文，文中将两者进行对比，列举了PHP优于<a href="http://www.nodejs.org" target="_blank">Node.js</a>的五大理由。一起来看下。<span id="more-922"></span></p>
<p><a href="http://www.hdj.me/wp-content/uploads/2012/04/17405301500839320.jpg"><img src="http://www.hdj.me/wp-content/uploads/2012/04/17405301500839320.jpg" alt="" title="17405301500839320" width="550" height="176" class="alignnone size-full wp-image-923" /></a></p>
<p><strong>1. 容易托管</strong></p>
<p>大多数Web托管服务器提供商能为PHP提供托管，而对于Node，你需要找一个更专业的托管服务提供商。通常情况下，需要通过shell访问来设置应用程序，并且大部分托管提供商不包含这些，即便是包含此功能的，其他的包要相对的廉价些。</p>
<p><strong>2. PHP在服务器上安装更方便、简易</strong></p>
<p>PHP可轻易的安装WAMP，LAMP或者MAMP，将代码部署到一个Web虚拟主机，只需要拖放文件，就可大功告成。</p>
<p>虽然，Node本身并不难安装，但是仍需要具备更多的专业知识才能将它设置好，为了能够在服务器上安装，你通常需要了解一些Linux系统管理员方面的知识，以便你在安装过程中当系统奔溃时确保node能够很好的运行。</p>
<p><strong>3. 如果PHP代码损坏，不会拖垮整个服务器</strong></p>
<p>PHP代码只运行在自己的进程范围中，当某个请求显示错误时，它只对特定的请求产生影响。而在Node环境中，所有的请求均在单一的进程服务器中，当某个请求导致未知错误时，整个服务器都会受到影响。</p>
<p><strong>4. PHP进程短暂</strong></p>
<p>在PHP中每个进程对请求持续的时间很短暂，这就意味着你不必为资源配置和内存而担忧。而Node在进程过程中需要运行很长一段时间，你需要小心并妥善管理好内存。比如，如果你忘记从全局数据中删除条目，这会轻易的导致你将内存泄露。</p>
<p><strong>5．更大的标准库</strong></p>
<p>PHP的标准库比Node的标准库要大的多。</p>
<p><strong>结束语：</strong></p>
<p>当然，并不是说PHP在各个方面都优于Node，在某些事情上，Node还是很不错的（比如，信息实时处理方面），Node是一个有趣的产品，它有一个优雅的架构。尽管Node.js 不是银弹，但它仍然是颗子弹。</p>
<p>开发者们对编程语言的喜好总是各有千秋，每一门语言都有它存在的价值，笔者认为选择自己最适合的才是最重要的。除了以上这些，PHP还在哪些方面优于Node呢？欢迎您在评论中列出。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hdj.me/php-better-then-nodejs/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>几个PHP算法题</title>
		<link>http://www.hdj.me/some-php-algorithm</link>
		<comments>http://www.hdj.me/some-php-algorithm#comments</comments>
		<pubDate>Sun, 22 Apr 2012 16:52:46 +0000</pubDate>
		<dc:creator>huangdijia</dc:creator>
				<category><![CDATA[PHP/PHP框架]]></category>
		<category><![CDATA[php分苹果]]></category>
		<category><![CDATA[php汉诺塔]]></category>
		<category><![CDATA[php算法]]></category>
		<category><![CDATA[php选猴王]]></category>

		<guid isPermaLink="false">http://www.hdj.me/?p=913</guid>
		<description><![CDATA[有5个人偷了一堆苹果，准备在第二天分赃。晚上，有一人遛出来，把所有菜果分成5份，但是多了一个，顺手把这个扔给树上的猴了，自己先拿1/5藏了。没想到其他四人也都是这么想的，都如第一个人一样分成5份把多的那一个扔给了猴，偷走了1/5。第二天，大家分赃，也是分成5份多一个扔给猴了。最后一人分了一份。问：共有多少苹果？ PHP代码如下： for ($i = 1; ; $i++) { if ($i%5 == 1) { //第一个人取五分之一，还剩$t $t = $i - round($i/5) - 1; if($t % 5 == 1) { //第二个人取五分之一，还剩$r $r = $t - round($t/5) - 1; if($r % 5 == 1) { //第三个人取五分之一，还剩$s $s = $r - round($r/5) - 1; if($s % 5 == 1) [...]]]></description>
			<content:encoded><![CDATA[<p>有5个人偷了一堆苹果，准备在第二天分赃。晚上，有一人遛出来，把所有菜果分成5份，但是多了一个，顺手把这个扔给树上的猴了，自己先拿1/5藏了。没想到其他四人也都是这么想的，都如第一个人一样分成5份把多的那一个扔给了猴，偷走了1/5。第二天，大家分赃，也是分成5份多一个扔给猴了。最后一人分了一份。问：共有多少苹果？<span id="more-913"></span><br />
PHP代码如下：</p>
<pre>for ($i = 1; ; $i++)
{
    if ($i%5 == 1) {
        //第一个人取五分之一，还剩$t
        $t = $i - round($i/5) - 1;
        if($t % 5 == 1)
        {
            //第二个人取五分之一，还剩$r
            $r = $t - round($t/5) - 1;
            if($r % 5 == 1)
            {
                //第三个人取五分之一，还剩$s
                $s = $r - round($r/5) - 1;
                if($s % 5 == 1)
                {
                    //第四个人取五分之一，还剩$x
                    $x = $s - round($s/5) - 1;
                    if($x % 5 == 1)
                    {
                        //第五个人取五分之一，还剩$y
                        $y = $x - round($x/5) - 1;
                        if ($y % 5 == 1) {
                            echo $i;
                            break;
                        }
                    }
                }
            }
        }
    }
}</pre>
<p>一群猴子排成一圈，按1，2，&#8230;，n依次编号。然后从第1只开始数，数到第m只,把它踢出圈，从它后面再开始数，再数到第m只，在把它踢出去&#8230;，如此不停的进行下去，直到最后只剩下一只猴子为止，那只猴子就叫做大王。要求编程模拟此过程，输入m、n, 输出最后那个大王的编号。</p>
<pre>function king($n, $m){
    $monkeys = range(1, $n);
    $i=0;
    $k=$n;
    while (count($monkeys)>1) {
        if(($i+1)%$m==0) {
            unset($monkeys[$i]);
        } else {
            array_push($monkeys,$monkeys[$i]);
            unset($monkeys[$i]);
        }
        $i++;
    }
    return current($monkeys);
}

$a = king(5, 2);
var_dump($a);</pre>
<p>汉诺塔（又称河内塔）问题是印度的一个古老的传说。开天辟地的神勃拉玛在一个庙里留下了三根金刚石的棒，第一根上面套着64个圆的金片，最大的一个在底下，其余一个比一个小，依次叠上去，庙里的众僧不倦地把它们一个个地从这根棒搬到另一根棒上，规定可利用中间的一根棒作为帮助，但每次只能搬一个，而且大的不能放在小的上面。解答结果请自己运行计算，程序见尾部。面对庞大的数字(移动圆片的次数)18446744073709551615，看来，众僧们耗尽毕生精力也不可能完成金片的移动。<br />
后来，这个传说就演变为汉诺塔游戏:<br />
1.有三根杆子A,B,C。A杆上有若干碟子<br />
2.每次移动一块碟子,小的只能叠在大的上面<br />
3.把所有碟子从A杆全部移到C杆上<br />
经过研究发现，汉诺塔的破解很简单，就是按照移动规则向一个方向移动金片：<br />
如3阶汉诺塔的移动：A→C,A→B,C→B,A→C,B→A,B→C,A→C<br />
此外，汉诺塔问题也是程序设计中的经典递归问题。</p>
<pre>function hanoi($n,$x,$y,$z){
    if($n==1){
        echo 'move disk 1 from '.$x.' to '.$z."\n";
    }else{
        hanoi($n-1,$x,$z,$y);
        echo 'move disk '.$n.' from '.$x.' to '.$z."\n";
        hanoi($n-1,$y,$x,$z);
    }
}
hanoi(3,'A','B','C'); </pre>
]]></content:encoded>
			<wfw:commentRss>http://www.hdj.me/some-php-algorithm/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>global static 和$GLOBALS使用与区别</title>
		<link>http://www.hdj.me/global-static-globals-diff-in-php</link>
		<comments>http://www.hdj.me/global-static-globals-diff-in-php#comments</comments>
		<pubDate>Fri, 20 Apr 2012 12:08:31 +0000</pubDate>
		<dc:creator>huangdijia</dc:creator>
				<category><![CDATA[PHP/PHP框架]]></category>

		<guid isPermaLink="false">http://www.hdj.me/?p=910</guid>
		<description><![CDATA[1.global在整个页面起作用。 2.static只在function和class内起作用。 global和$GLOBALS使用基本相同，但在实际开发中大不相同。 global在函数产生一个指向函数外部变量的别名变量，而不是真正的函数外部变量，一但改变了别名变量的指向地址，就会发生一些意料不到情况，例如例子1. $GLOBALS[]确确实实调用是外部的变量，函数内外会始终保持一致！]]></description>
			<content:encoded><![CDATA[<p>1.global在整个页面起作用。<br />
2.static只在function和class内起作用。<br />
global和$GLOBALS使用基本相同，但在实际开发中大不相同。<br />
global在函数产生一个指向函数外部变量的别名变量，而不是真正的函数外部变量，一但改变了别名变量的指向地址，就会发生一些意料不到情况，例如例子1.<br />
$GLOBALS[]确确实实调用是外部的变量，函数内外会始终保持一致！</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hdj.me/global-static-globals-diff-in-php/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>怎样把自己培养成为一个优秀的程序员</title>
		<link>http://www.hdj.me/how-to-be-nice-programer</link>
		<comments>http://www.hdj.me/how-to-be-nice-programer#comments</comments>
		<pubDate>Sun, 15 Apr 2012 01:51:03 +0000</pubDate>
		<dc:creator>huangdijia</dc:creator>
				<category><![CDATA[收藏夹]]></category>

		<guid isPermaLink="false">http://www.hdj.me/?p=906</guid>
		<description><![CDATA[态度篇 1. 做实事：不要抱怨，发牢骚，指责他人，找出问题所在，想办法解决。对问题和错误，要勇于承担。 2. 欲速则不达：用小聪明、权宜之计解决问题，求快而不顾代码质量，会给项目留下要命的死角。 3. 对事不对人：就事论事，明智、真诚、虚心地讨论问题，提出创新方案。 4. 排除万难，奋勇前进：勇气往往是克服困难的唯一方法。 学习篇 5. 跟踪变化：新技术层出不穷并不可怕。坚持学习新技术，读书，读技术杂志，参加技术活动，与人交流。要多理解新词背后的所以然，把握技术大趋势，将新技术用 于产品开发要谨慎。 6. 对团队投资：打造学习型团队，不断提高兄弟们的平均水平。 7. 懂得丢弃：老的套路和技术，该丢，就得丢。不要固步自封。 8. 打破砂锅问到底：不断追问，真正搞懂问题的本质。为什么？应该成为你的口头禅。 9. 把握开发节奏：控制好时间，养成好习惯，不要加班。 开发流程篇 10. 让客户做决定：让用户在现场，倾听他们的声音，对业务最重要的决策应该让他们说了算。 11. 让设计指导而不是操纵开发：设计是前进的地图，它指引的是方向，而不是目的本身。设计的详略程度应该适当。 12. 合理地使用技术：根据需要而不是其他因素选择技术。对各种技术方案进行严格地追问，真诚面对各种问题。 13. 让应用随时都可以发布：通过善用持续集成和版本管理，你应该随时都能够编译、运行甚至部署应用。 14. 提早集成，频繁集成：集成有风险，要尽早尽量多地集成。 15. 提早实现自动化部署 16. 使用演示获得频繁反馈 17. 使用短迭代，增量发布 18. 固定价格就意味着背叛承诺：估算应该基于实际的工作不断变化。 用户篇 19. 守护天使：自动化单元测试是你的守护天使。 20. 先用它再实现它：测试驱动开发其实是一种设计工具。 21. 不同环境，就有不同问题：要重视多平台问题。 22. 自动验收测试 23. 度量真实的进度：在工作量估算上，不要自欺欺人。 24. 倾听用户的声音：每一声抱怨都隐藏着宝贵的真理。 编程篇 25. [...]]]></description>
			<content:encoded><![CDATA[<p><strong>态度篇</strong><br />
 1. 做实事：不要抱怨，发牢骚，指责他人，找出问题所在，想办法解决。对问题和错误，要勇于承担。<br />
 2. 欲速则不达：用小聪明、权宜之计解决问题，求快而不顾代码质量，会给项目留下要命的死角。<br />
 3. 对事不对人：就事论事，明智、真诚、虚心地讨论问题，提出创新方案。<br />
 4. 排除万难，奋勇前进：勇气往往是克服困难的唯一方法。</p>
<p><strong>学习篇</strong><br />
 5. 跟踪变化：新技术层出不穷并不可怕。坚持学习新技术，读书，读技术杂志，参加技术活动，与人交流。要多理解新词背后的所以然，把握技术大趋势，将新技术用 于产品开发要谨慎。<br />
 6. 对团队投资：打造学习型团队，不断提高兄弟们的平均水平。<br />
 7. 懂得丢弃：老的套路和技术，该丢，就得丢。不要固步自封。<br />
 8. 打破砂锅问到底：不断追问，真正搞懂问题的本质。为什么？应该成为你的口头禅。<br />
 9. 把握开发节奏：控制好时间，养成好习惯，不要加班。<span id="more-906"></span></p>
<p><strong>开发流程篇</strong><br />
 10. 让客户做决定：让用户在现场，倾听他们的声音，对业务最重要的决策应该让他们说了算。<br />
 11. 让设计指导而不是操纵开发：设计是前进的地图，它指引的是方向，而不是目的本身。设计的详略程度应该适当。<br />
 12. 合理地使用技术：根据需要而不是其他因素选择技术。对各种技术方案进行严格地追问，真诚面对各种问题。<br />
 13. 让应用随时都可以发布：通过善用持续集成和版本管理，你应该随时都能够编译、运行甚至部署应用。<br />
 14. 提早集成，频繁集成：集成有风险，要尽早尽量多地集成。<br />
 15. 提早实现自动化部署<br />
 16. 使用演示获得频繁反馈<br />
 17. 使用短迭代，增量发布<br />
 18. 固定价格就意味着背叛承诺：估算应该基于实际的工作不断变化。</p>
<p><strong>用户篇</strong><br />
 19. 守护天使：自动化单元测试是你的守护天使。<br />
 20. 先用它再实现它：测试驱动开发其实是一种设计工具。<br />
 21. 不同环境，就有不同问题：要重视多平台问题。<br />
 22. 自动验收测试<br />
 23. 度量真实的进度：在工作量估算上，不要自欺欺人。<br />
 24. 倾听用户的声音：每一声抱怨都隐藏着宝贵的真理。</p>
<p><strong>编程篇</strong><br />
 25. 代码要清晰地表达意图：代码是给人读的，不要耍小聪明。<br />
 26. 用代码沟通：注释的艺术。<br />
 27. 动态地进行取舍：记住，没有最佳解决方案。各种目标不可能面面俱到，关注对用户重要的需求。<br />
 28. 增量式编程：写一点代码就构建、测试、重构、休息。让代码干净利落。<br />
 29. 尽量简单：宁简勿繁。如果没有充足的理由，就不要使用什么模式、原则和特别的技术。<br />
 30. 编写内聚的代码：类和组件应该足够小，任务单一。<br />
 31. 告知，不要询问：多用消息传递，少用函数调用。<br />
 32. 根据契约进行替换：委托往往优于继承。</p>
<p><strong>调试篇</strong><br />
 33. 记录问题解决日志：不要在同一地方摔倒两次。错误是最宝贵的财富。<br />
 34. 警告就是错误：忽视编译器的警告可能铸成大错。<br />
 35. 对问题各个击破：分而治之是计算机科学中最重要的思想之一。但是，要从设计和原型阶段就考虑各部分应该能够很好地分离。<br />
 36. 报告所有的异常<br />
 37. 提供有用的错误信息：稍微多花一点心思，出错的时候，将给你带来极大便利。</p>
<p><strong>团队协作篇</strong><br />
 38. 定期安排会面时间：常开会，开短会。<br />
 39. 架构师必须写代码：不写代码的架构师不是好架构师。好的设计都来自实际编程。编程可以带来深入的理解。<br />
 40. 实行代码集体所有制：让开发人员在系统不同区域中不同的模块和任务之间轮岗。<br />
 41. 成为指导者：教学相长。分享能提高团队的总体能力。<br />
 42. 让大家自己想办法：指引方向，而不是直接提供解决方案。让每个人都有机会在干中学习。<br />
 43. 准备好后再共享代码：不要提交无法编译或者没有通过单元测试的代码！<br />
 44. 做代码复查：复查对提高代码质量、减少错误极为重要。<br />
 45. 及时通报进展与问题：主动通报，不要让别人来问你。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hdj.me/how-to-be-nice-programer/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

