首页 > 代码库 > PHP常见面试题汇总

PHP常见面试题汇总

目录:
1、如何使用PHP导入导出csv?
2、php接收POST数据的方式有哪些?
3、如何让json_encode()不转义斜杠?我在做服务器返回一些数据时需要返回一些地址,但是默认的json_code是会对 / 转义成 \/ 的
4、新浪微博、QQ空间、今日头条PC版、蘑菇街、美丽说等网站是如何实现滚屏加载的?即:无刷新动态加载数据技术的应用
5、移动浏览器的判断方法(通用)
6、php中如何优化多个 if...elseif...elseif...else...  语句的情况?
7、php如何检测当前是不是正在使用HTTPS协议?
8、php如何实现在数组$items的任意位置插入数据?
9、如何快速提取URL中的域名?如下 $url=‘http://bbs.sijiaomao.com/read.php?tid=942&fid=22‘;如何快速提取 "bbs.sijiaomao.com"
10、如果用foreach来访问PHP的数组, 遍历的顺序是固定的么? 有什么规律可循呢 ?

11、php中如何使用file_get_contents()发送post请求?

12、如何判断php是线程安全还是非线程安全的?

13、php线程安全与非线程安全版本的区别?

14、Windows下的PHP开发环境搭建——PHP线程安全与非线程安全、Apache版本选择,及详解五种运行模式。

15、线程与进程的区别

16、FastCgi与PHP-fpm之间是个什么样的关系

17、PHP的线程安全模式(Thread Safety)

1、如何使用php导入导出csv?

 

[php] view plain copy
 
 print?技术分享技术分享
  1. <?php   
  2.   
  3. //读取csv文件中数据:使用php内置函数fgetcsv()  
  4. function input_csv($handle) {   
  5.     $out = array ();   
  6.     $n = 0;   
  7.     while ($data = fgetcsv($file_handle, 10000)) {   
  8.         $num = count($data);   
  9.         for ($i = 0; $i < $num; $i++) {   
  10.             $out[$n][$i] = $data[$i];   
  11.         }   
  12.         $n++;   
  13.     }   
  14.     return $out;   
  15. }   
  16.   
  17.   
  18. //导出数据到csv  
  19. function export_csv($filename,$data) {   
  20.     header("Content-type:text/csv");   
  21.     header("Content-Disposition:attachment;filename=".$filename);   
  22.     header(‘Cache-Control:must-revalidate,post-check=0,pre-check=0‘);   
  23.     header(‘Expires:0‘);   
  24.     header(‘Pragma:public‘);   
  25.     echo $data;   
  26. }   
  27.   
  28. ?>  

2、php接收POST数据的方式有哪些?

通常情况下用户使用浏览器网页表单向服务器post提交数据,我们使用PHP接收用户POST到服务器的数据,并进行适当的处理。但有些情况下,如用户使用客户端软件向服务端php程序发送post数据,而不能用$_POST来识别,那又该如何处理呢?

①$_POST方式接收数据

$_POST方式是通过 HTTP POST 方法传递的变量组成的数组,是自动全局变量。如使用$_POST[‘name‘]就可以接收到网页表单以及网页异步方式post过来的数据,即$_POST只能接收文档类型为Content-Type: application/x-www-form-urlencoded提交的数据。

②$GLOBALS[‘HTTP_RAW_POST_DATA‘]方式接收数据

如果用过post过来的数据不是PHP能够识别的文档类型,比如 text/xml 或者 soap 等等,我们可以用$GLOBALS[‘HTTP_RAW_POST_DATA‘]来接收。$HTTP_RAW_POST_DATA 变量包含有原始的POST数据。此变量仅在碰到未识别MIME 类型的数据时产生。$HTTP_RAW_POST_DATA 对于enctype="multipart/form-data" 表单数据不可用。也就是说使用$HTTP_RAW_POST_DATA无法接收网页表单post过来的数据。

③php://input方式接收数据

如果访问原始 POST 数据的更好方法是 php://input。php://input 允许读取 POST 的原始数据。和 $HTTP_RAW_POST_DATA 比起来,它给内存带来的压力较小,并且不需要任何特殊的php.ini设置,而php://input不能用于 enctype="multipart/form-data"。

例如,用户使用某个客户端应用程序post给服务器一个文件,文件的内容我们不管它,但是我们要把这个文件完整的保存在服务器上,我们可以使用如下代码:

 

[php] view plain copy
 
 print?技术分享技术分享
  1. $input = file_get_contents(‘php://input‘);   
  2. file_put_contents($original, $input); //$original为服务器上的文件  

以上代码使用file_get_contents(‘php://input‘)接收post数据,然后将数据写入$original文件中,其实可以理解为从客户端上传了一个文件到服务器上,此类应用非常多,尤其是我们PHP开发要与C,C++等应用程序开发进行产品联合开发时会用到,例如本站有文章:拍照上传就是结合flash利用此原理来上传照片的。

以下是一个小示例,演示了$_POST,$GLOBALS[‘HTTP_RAW_POST_DATA‘]和php://input三种不同方式的接收POST数据处理:

a.html

 

[php] view plain copy
 
 print?技术分享技术分享
  1. <form name="demo_form" action="post.php" method="post">   
  2.     <p><label>Name: </label><input type="text" class="input" name="name"></p>   
  3.     <p><label>Address: </label><input type="text" class="input" name="address"></p>   
  4.     <p><input type="submit" name="submit" class="btn" value=http://www.mamicode.com/"Submit"></p>   
  5. </form>   

技术分享

 

post.php

 

[php] view plain copy
 
 print?技术分享技术分享
  1. header("Content-type:text/html;charset=utf-8");   
  2.    
  3. echo ‘$_POST接收:<br/>‘;   
  4. print_r($_POST);   
  5. echo ‘<hr/>‘;   
  6.    
  7. echo ‘$GLOBALS[\‘HTTP_RAW_POST_DATA\‘]接收:<br/>‘;   
  8. print_r($GLOBALS[‘HTTP_RAW_POST_DATA‘]);   
  9. echo ‘<hr/>‘;   
  10.    
  11. echo ‘php://input接收:<br/>‘;   
  12. $data = file_get_contents(‘php://input‘);   
  13. print_r(urldecode($data));  


输出结果:

 

$_POST接收:
Array( [name] => xiaoqiang [address] => huilongguan [submit] => Submit)


$GLOBALS[‘HTTP_RAW_POST_DATA‘]接收:


php://input接收:
name=xiaoqiang&address=huilongguan&submit=Submit

 

3、如何让json_encode()不转义斜杠?我在做服务器返回一些数据时需要返回一些地址,但是默认的json_code是会对 / 转义成 \/ 的,所以解决办法有如下两种:

 

[php] view plain copy
 
 print?技术分享技术分享
  1. echo str_replace("\\/", "/",  json_encode("<font color=‘red‘>www.baidu.com<\/font>"));  //第一种方式:正则替换  
  2.   
  3. echo json_encode("<font color=‘red‘>www.baidu.com<\/font>", JSON_UNESCAPED_SLASHES);    //第二种方式:php5.4+  
  4.   
  5. echo json_encode("百度", JSON_UNESCAPED_UNICODE);     //汉字不转义为\u开头的UNICODE数据,必须PHP5.4+  

4、新浪微博、QQ空间、今日头条PC版、蘑菇街、美丽说等网站是如何实现滚屏加载的?即:无刷新动态加载数据技术的应用

 

[php] view plain copy
 
 print?技术分享技术分享
  1. //①、首先,我们要获取浏览器可视区域页面的高度  
  2. var winH = $(window).height();  
  3. //②、然后,当滚动页面的时候需要做的事情是:计算页面总高度(当滚动底部时,页面新加载数据,所以页面总高度是动态变化的),计算滚动条位置(滚动条位置也是随着加载页面的高度动态变化的),然后构造一个公式,计算相对比例。  
  4. $(window).scroll(function () {   
  5.     var pageH = $(document.body).height(); //页面总高度   
  6.     var scrollT = $(window).scrollTop(); //滚动条top   
  7.     var aa = (pageH-winH-scrollT)/winH;//aa值越小,滚动条越接近底部  理想状态下:页面总高度 = 浏览器可视区域页面高度 + 滚动条正好在底部时的top高度  
  8. });   
  9. //③、当滚动条接近页底时,触发ajax加载,在本例中我们使用jQuery的getJSON方法,向服务端result.php发送请求,请求的参数为page,即页数。  
  10. if(aa<0.02){   
  11.     $.getJSON("result.php",{page:i},function(json){   
  12.         .....   
  13.     });   
  14.  }   
  15.  //④、如果请求响应成功返回JSON数据,则解析JSON数据,并将数据追加到页面DIV#container后,如果没有JSON数据返回,则说明数据全部显示完毕。  
  16.  if(json){   
  17.     var str = "";   
  18.     $.each(json,function(index,array){ //遍历   
  19.         var str = "..."; //获取的JSON数据   
  20.         $("#container").append(str); //追加   
  21.     });   
  22.     i++;  //页数+1   
  23. }else{   
  24.     $(".nodata").show().html("别滚动了,已经到底了。。。");   
  25.     return false;   
  26. }   
  27. //完整的代码  
  28. $(function(){   
  29.     var winH = $(window).height(); //页面可视区域高度   
  30.     var i = 1; //设置当前页数   
  31.     $(window).scroll(function () {   
  32.         var pageH = $(document.body).height();   
  33.         var scrollT = $(window).scrollTop(); //滚动条top   
  34.         var aa = (pageH-winH-scrollT)/winH;   
  35.         if(aa<0.02){   
  36.             $.getJSON("result.php",{page:i},function(json){   
  37.                 if(json){   
  38.                     var str = "";   
  39.                     $.each(json,function(index,array){   
  40.                         var str = "<div class=\"single_item\"><div class=\"element_head\">";   
  41.                         var str += "<div class=\"date\">"+array[‘date‘]+"</div>";   
  42.                         var str += "<div class=\"author\">"+array[‘author‘]+"</div>";   
  43.                         var str += "</div><div class=\"content\">"+array[‘content‘]+"</div></div>";   
  44.                         $("#container").append(str);   
  45.                     });   
  46.                     i++;   
  47.                 }else{   
  48.                     $(".nodata").show().html("别滚动了,已经到底了。。。");   
  49.                     return false;   
  50.                 }   
  51.             });   
  52.         }   
  53.     });   
  54. });   
[php] view plain copy
 
 print?技术分享技术分享
  1. $(window).scroll(function(){  
  2.   var scrollTop = $(this).scrollTop();  
  3.   var scrollHeight = $(document).height();  
  4.   var windowHeight = $(this).height();  
  5.   if(scrollTop + windowHeight == scrollHeight){  
  6.     alert("you are in the bottom");  
  7.   }  
  8. });  

5、移动浏览器的判断方法(通用)

 

[php] view plain copy
 
 print?技术分享技术分享
  1. <?php  
  2. /** 
  3.  * 移动浏览器判断 
  4.  * 
  5.  * @return bool 
  6.  */  
  7. function browse_is_mobile () {  
  8.     return false;  
  9.   
  10.     $user_agent = $_SERVER[‘HTTP_USER_AGENT‘];  
  11.     if(preg_match("/iPhone/i", $user_agent) || preg_match("/iPod/i", $user_agent) ||  preg_match("/Android/i", $user_agent)){  
  12.         return true;  
  13.     }  
  14.     else {  
  15.         return false;  
  16.     }  
  17. }  
  18.   
  19. /** 
  20.  * 检测浏览器类型 
  21.  * 
  22.  * @return bool|int 
  23.  */  
  24. function check_mobile_browse () {  
  25.     $user_agent = $_SERVER[‘HTTP_USER_AGENT‘];  
  26.     if(preg_match("/iPhone/i", $user_agent) || preg_match("/iPod/i", $user_agent)){  
  27.         return 1;  
  28.     }  
  29.     elseif (preg_match("/Android/i", $user_agent)) {  
  30.         return 2;  
  31.     }  
  32.     else {  
  33.         return false;  
  34.     }  
  35. }  
  36. ?>  

6、php中如何优化多个 if...elseif...elseif...else...  语句的情况?

 

[php] view plain copy
 
 print?技术分享技术分享
  1. <?php  
  2.   
  3. //对于很多时候我们需要用N多个if,elseif,elseif…来达到连续判定完后取出某个值的效果,例如:  
  4. $data = array();  
  5. if("login"==$type){  
  6.     $data = array("uid"=>1,"pwd"=>123);  
  7. }elseif("register"==$type){  
  8.     $data = array("username"=>"max","new_pwd"=>432);  
  9. }elseif("put_info"==$type){  
  10.     $data = array("something"=>"Hello");  
  11. }  
  12.   
  13. //向上面这样的连续判断效率较低,有些人会说改成switch case 的写法,效率会高过if,但是依然是十分的繁琐,如下:  
  14. $data = array();  
  15. switch($type){  
  16.     case "login":  
  17.     $data = array("uid"=>1,"pwd"=>123);  
  18.     break;  
  19.     case "register":  
  20.     $data = array("username"=>"max","new_pwd"=>432);  
  21.     break;  
  22.     case "put_info":  
  23.     $data = array("something"=>"Hello");  
  24.     break;  
  25. }  
  26.   
  27.   
  28. //但是这样写依然会比较麻烦,且运行效率也不会太高,当数据量较大的时候速度会比较慢,如上程序改成如下样式:  
  29. //优点:在php中数组使用的是hashtable,时间复杂度是O(1),运行速度远远高于以上两种,另外书写起来也更简便的多。即使传入的参数是未定义的,也不会浪费大量的系统资源  
  30. $data_arr = array(  
  31.     "login"     =>   array("uid"=>1,"pwd"=>123),  
  32.     "register"  =>   array("username"=>"max","new_pwd"=>432),  
  33.     "put_info"  =>   array("something"=>"Hello")  
  34. );  
  35.   
  36. if(isset($data_arr[$type])){  
  37.     $data = $data_arr[$type];  
  38. }  
  39.   
  40. ?>  

7、php如何检测当前是不是正在使用HTTPS协议?

[php] view plain copy
 
 print?技术分享技术分享
  1. <?php  
  2. function is_HTTPS(){    
  3.     if(!isset($_SERVER[‘HTTPS‘]))  return FALSE;    
  4.     if($_SERVER[‘HTTPS‘] === 1){  //Apache    
  5.         return TRUE;    
  6.     }elseif($_SERVER[‘HTTPS‘] === ‘on‘){ //IIS    
  7.         return TRUE;    
  8.     }elseif($_SERVER[‘SERVER_PORT‘] == 443){ //其他    
  9.         return TRUE;    
  10.     }    
  11.     return FALSE;    
  12. }  
  13. ?>  

8、php如何实现在数组$items的任意位置插入数据?

 

[php] view plain copy
 
 print?技术分享技术分享
  1. <?php  
  2.   
  3. $items= [‘苹果‘,‘橘子‘,‘梨‘,‘菠萝‘,‘香蕉‘,‘火龙果‘] ;  
  4.   
  5. /* 
  6. $items 传入的数组 
  7. $index 要插入的位置 
  8. $value 要插入的数据 
  9. */  
  10. function insertAt($items, $index, $value) {  
  11.     return $items;  
  12. }  
  13.   
  14. $items=insertAt($items, 2 , ‘橙子‘); //执行完成后,结果如下: [‘苹果‘,‘橘子‘,‘橙子‘,‘梨‘,‘菠萝‘,‘香蕉‘,‘火龙果‘] ;  
  15.   
  16. ?>  

9、如何快速提取URL中的域名?如下 $url=‘http://bbs.sijiaomao.com/read.php?tid=942&fid=22‘;如何快速提取 "bbs.sijiaomao.com"

[php] view plain copy
 
 print?技术分享技术分享
  1. <?php  
  2.   
  3.  echo parse_url($url, PHP_URL_HOST);  
  4.   
  5. ?>  

10、如果用foreach来访问PHP的数组, 遍历的顺序是固定的么? 有什么规律可循呢 ?

 

[php] view plain copy
 
 print?技术分享技术分享
  1. <?php  
  2.   
  3. //PHP中遍历数组的顺序, 是和元素的添加先后相关的! 所以不管是索引数组还是关联数组,结果都一样,添加的顺序就是依据!  
  4. 至于为什么会这样,就需要深入探寻数组的底层实现原理了  
  5. $items[2]  = 2015;  
  6. $items[1]  = 2014;  
  7. $items[0]  = 2013;  
  8.   
  9. foreach ($items as $key => $val) {  
  10.     //结果是什么?  
  11.     echo $key,‘ => ‘ ,$val, "\n";  
  12. }  
  13.   
  14. ?>  

11、php中如何使用file_get_contents()发送post请求?

 

[php] view plain copy
 
 print?技术分享技术分享
  1. <?php  
  2.   
  3. //我们知道PHP中的file_get_contents无比强大,不仅仅能读取文件内容,还能设置超时时间采集网页数据。那么,能否用file_get_contents来发送post请求呢,如果能,改如何写?  
  4. $postdata = http_build_query(  
  5.     array(  
  6.         ‘username‘ => ‘号外‘,  
  7.         ‘password‘ => ‘123456‘  
  8.     )  
  9. );  
  10.   
  11. $opts = array(‘http‘ =>  
  12.     array(  
  13.         ‘method‘  => ‘POST‘,  
  14.         ‘header‘  => ‘Content-type: application/x-www-form-urlencoded‘,  
  15.         ‘content‘ => $postdata  
  16.     )  
  17. );  
  18.   
  19. $context  = stream_context_create($opts);  
  20.   
  21. $result = file_get_contents(‘http://www.baidu.com/server.php‘, false, $context);  
  22.   
  23. ?>  

13、如何判断php是线程安全还是非线程安全的?

什么是线程安全与非线程安全?

线程安全就是在多线程环境下也不会出现数据不一致,而非线程安全就有可能出现数据不一致的情况。

线程安全由于要确保数据的一致性,所以对资源的读写进行了控制,换句话说增加了系统开销。所以在单线程环境中效率比非线程安全的效率要低些,但是如果线程间数据相关,需要保证读写顺序,用线程安全模式

这个主要是针对web server 而言,在windows环境下,如果你使用的web server 是apchae 或者 iis 7以下版本,则应该选择线程安全的安装文件,而如果你使用Fast-cgi模式时,可以选择非线程安全,因为 web sever 本身能保证线程安全。
当然还有二进制文件编译时所使用的编译器:vc9 (vs系列) vc6(gcc)

如楼上所言,是针对web server的,部分web server在处理应用请求的时候是用多线程而非多进程的方式处理,线程方式因为涉及到共享寄存器和内存,所以很容易出错,这个时候程序就需要花一些额外的经历去处理寄存器中的数据一致性,即保证线程安全。
所以是否采用线程安全主要看你的web server所采用的PHP请求处理方式,如果是多线程处理,那么请选择线程安全的,否则选择非线程安全的,如楼上所说Fast-cgi方式可选择非线程安全的

先从字面意思上理解,None-Thread Safe就是非线程安全,在执行时不进行线程(thread)安全检查;Thread Safe就是线程安全,执行时会进行线程(thread)安全检查,以防止有新要求就启动新线程的 CGI 执行方式耗尽系统资源。

再来看PHP的两种执行方式:ISAPI和FastCGI。FastCGI执行方式是以单一线程来执行操作,所以不需要进行线程的安全检查,除去线程安全检查的防护反而可以提高执行效率,所以,如果是以 FastCGI(无论搭配 IIS 6 或 IIS 7)执行 PHP ,都建议下载、执行 non-thread safe 的 PHP (PHP 的二進位檔有兩種包裝方式:msi 、zip ,請下載 zip 套件)。而线程安全检查正是为ISAPI方式的PHP准备的,因为有许多php模块都不是线程安全的,所以需要使用Thread Safe的PHP。

说到这里,大家应该知道应该如何选择哪个版本的PHP了。None-Thread Safe or Thread Safe,您会选择哪个?

Windows下的PHP现在的版本已经是5.3.6,现在的Windows版本可以在PHP官方PHP For Windows(http://windows.php.NET/download/)下载,下载的时候同版本有VC9 x86 Non Thread Safe、VC9 x86 Thread Safe、VC6 x86 Non Thread Safe、VC6 x86 Thread Safe等四个版本。那么这些版本有什么区别呢?

VC9的版本是用legacy VS 2008编译的,VC6的版本是用legacy VS6编译的。

如果你是在windows下使用IIS+PHP的话,你需要下载VC9的版本。
如果你是在windows下使用Apache+PHP的话,你需要下载VC6的版本。

Non Thread Safe是指非线程安全,Thread Safe则是指线程安全。

如果是使用ISAPI的方式来运行PHP就必须用Thread Safe(线程安全)的版本;而用FastCGI模式运行PHP的话就没有必要用线程安全检查了,用None Thread Safe(NTS,非线程安全)的版本能够更好的提高效率。

如何判断:

技术分享

通过phpinfo(); 查看其中的 Thread Safety 项,这个项目就是查看是否是线程安全,如果是:enabled,一般来说应该是ts版,否则是nts版。

13、php线程安全与非线程安全版本的区别是什么?

Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分,这两者不同在于何处?到底应该用哪种?这里做一个简单的介绍

从2000年10月20日发布的第一个Windows版的PHP3.0.17开始的都是线程安全的版本,这是由于与Linux/Unix系统是采用多进程的工作方式不同的是Windows系统是采用多线程的工作方式。如果在IIS下以CGI方式运行PHP会非常慢,这是由于CGI模式是建立在多进程的基础之上的,而非多线程。

一般我们会把PHP配置成以ISAPI的方式来运行,ISAPI是多线程的方式,这样就快多了。但存在一个问题,很多常用的PHP扩展是以Linux/Unix的多进程思想来开发的,这些扩展在ISAPI的方式运行时就会出错搞垮IIS。因此在IIS下CGI模式才是PHP运行的最安全方式,但CGI模式对于每个HTTP请求都需要重新加载和卸载整个PHP环境,其消耗是巨大的。

为了兼顾IIS下PHP的效率和安全,微软给出了FastCGI的解决方案。FastCGI可以让PHP的进程重复利用而不是每一个新的请求就重开一个进程。同时FastCGI也可以允许几个进程同时执行。这样既解决了CGI进程模式消耗太大的问题,又利用上了CGI进程模式不存在线程安全问题的优势。

因此,如果是使用ISAPI的方式来运行PHP就必须用Thread Safe(线程安全)的版本;而用FastCGI模式运行PHP的话就没有必要用线程安全检查了,用None Thread Safe(NTS,非线程安全)的版本能够更好的提高效率。

PHP官方http://php.Net/上关于widows的版本有4个:VC9 x86 Non Thread Safe,VC9 x86 Thread Safe,VC6 x86 Non Thread Safe,VC6 x86 Thread Safe;那么有什么区别呢?

1.支持的服务器不同

VC9版本是针对IIS服务器的版本,没有对APACHE的支持,而VC6版本对IIS和apache都提供了支持

VC6 是什么?VC6 就是 legacy Visual Studio 6 compiler ,就是使用这个编译器编译的。
VC9 是什么?VC9 就是 the Visual Studio 2008 compiler ,就是用微软的 VS 编辑器编译的。

那我们如何选择下载哪个版本的 PHP 呢?

如果你是在 windows 下使用 Apache+PHP 的,请选择 VC6 版本;
如果你是在 windows 下使用 IIS+PHP 的,请选择 VC9 版本;

2.运行方式的不同

PHP有2中运行方式:ISAPI和FastCGI。

ISAPI执行方式是以DLL动态库的形式使用,可以在被用户请求后执行,在处理完一个用户请求后不会马上消失,所以需要进行线程安全检查,这样来提高程序的执行效率,所以如果是以ISAPI来执行PHP,建议选择Thread Safe版本;

而FastCGI执行方式是以单一线程来执行操作,所以不需要进行线程的安全检查,除去线程安全检查的防护反而可以提高执行效率,所以,如果是以FastCGI来执行PHP,建议选择Non Thread Safe版本。

对于apache服务器来说一般选择isapi方式,而对于nginx服务器则选择FastCGI方式。

3.总结

PHP所推出的Thread Safe主要针对的是Windows下以IIS来运行PHP的情况,因为Windows中频繁申请进程开销较大,所以在Windows中要以多线程方式来运转PHP,这时候就需要Thread Safe版本。而在Linux系统下,PHP绝大多数情况下都以多进程方式运行,所以直接使用None Thread Safe即可。

如果是使用ISAPI的方式来运行PHP就必须用Thread Safe(线程安全)的版本;而用FastCGI模式运行PHP的话就没有必要用线程安全检查了,用None Thread Safe(NTS,非线程安全)的版本能够更好的提高效率。

如果有多线程,就用线程安全版;如果只有一个线程,就用非线程安全版。

14、Windows下的PHP开发环境搭建——PHP线程安全与非线程安全、Apache版本选择,及详解五种运行模式


Windows下的PHP开发环境搭建——PHP线程安全与非线程安全、Apache版本选择,及详解五种运行模式。

今天为在Windows下建立PHP开发环境,在考虑下载何种PHP版本时,遭遇一些让我困惑的情况,为了解决这些困惑,不出意料地牵扯出更多让我困惑的问题。

为了将这些困惑一网打尽,我花了一下午加一晚上的时间查阅了大量资料,并做了一番实验后,终于把这些困惑全都搞得清清楚楚了。

说实话,之所以花了这么多时间,很大程度上是由于网上的资料几乎全都是支离破碎、以讹传讹的。既然我已经搞懂了,就花时间整理出来,即方便自己看,也便于大家阅读。相信通过这篇文章,可以解答很多在Windows下搭建PHP开发环境的朋友的困惑。

关于从何处下载Apache:

要安装Apache,你可能想当然地会去Apache官方网站下载适用于Windows的二进制版本。而这恰恰错了!

PHP官方不建议在Windows下安装从apache.org网站下载的Apache二进制安装包。原因是如果你使用来自apache.org的安装包,则由于这些安装包是基于陈旧的Visual Studio 6编译的,导致你不得不必须使用同样陈旧的PHP版本(即VC6的PHP版本。也即使用Visual Studio 6编译的PHP版本)才能与其配合使用。

要想使用最新版的PHP,应听从PHP的官方建议。PHP官方的建议是你在Windows下可以使用IIS,或者使用来自Apache Lounge(www.apachelounge.com)的Apache版本。

Apache Lounge所提供的Apache二进制安装包是使用VC11建立的。因此可搭配最新版本的PHP使用。

网上很多资料说如果你是在Windows下使用 Apache,则必须使用PHP的VC6版本,只有使用IIS时才能使用VC9及以上版本,完全是没有搞清情况的以讹传讹。

如何选择PHP版本(选择线程安全还是非线程安全):

在Windows下安装PHP,在选择PHP版本上很有讲究。

Windows下的PHP版本分两种:线程安全版本与非线程安全版本。

如果你打算使用IIS,则你可以以ISAPI或FastCGI这两种方式来安装PHP。CGI的方式因为效率低下,故不予考虑。

如果你要在IIS中以FastCGI方式使用PHP,则你应该使用PHP的非线程安全的版本(Non-Thread Safe,NTS)。原因是以FastCGI方式安装PHP时,PHP拥有独立的进程,并且FastCGI是单一线程的,不存在多个线程之间可能引发的相互干扰(这种干扰通常都是由于全局变量和静态变量导致的)。由于省去了线程安全的检查,因此使用FastCGI方式比ISAPI方式的效率更高一些。

如果你要在IIS中以ISAPI的方式使用PHP,则你应该使用PHP的线程安全版本(Thread Safe,TS)。原因是PHP以ISAPI方式安装时,PHP没有独立的进程,而是作为DLL被IIS加载运行的,即是依附于Web服务器进程的。当Web服务器运行在多线程模式下(IIS正是这种情况),PHP自然也就运行在多线程模式下。只要是在多线程模式下运行,就可能存在线程安全问题,因此应选择PHP的线程安全版本。

但在这里还有必要说明一下,尽管Apache本身是线程安全的,同时你也选择了PHP的线程安全版本,但由于一些Apache和PHP下的第三方扩展最初是基于Unix的多进程思想开发出来的,在设计开发时没有考虑线程安全的问题,因此,不排除在这种情况下仍然存在IIS被某些第三方扩展搞崩溃的可能。

如果你打算使用Apache,则你可以以模块、ISAPI、FastCGI这三种方式来安装PHP。CGI的方式因为效率低下,故不予考虑。

如果你要在Apache中以模块方式安装PHP,则你应该使用PHP的线程安全的版本。原因是当PHP作为Apache的模块安装时,PHP没有独立的进程,而是作为模块以DLL的形式被加载到Apache中的,是随Apache的启动而启动的,而Windows下的Apache为多线程工作模式,因此PHP自然也就运行在多线程模式下。因此,这种情况下应使用PHP的线程安全版本。

再来看ISAPI的情况。通常认为ISAPI是配合IIS使用的,因为ISAPI最初就是微软为IIS开发的。但Apache现在也可以通过加载mod_isapi.so模块来实现ISAPI的功能,以允许PHP以ISAPI的方式安装。.so文件是Apache自1.3版本后制定的用于Windows下的模块命名规则,对于Windows下的Apache而言,.so与.dll文件一样,都是动态链接库文件。

当要以ISAPI方式来安装PHP时,通常是加载一个名如phpXisapi.dll的DLL文件,其中的X为阿拉伯数字4、5等等这样子。

但一般不建议在Apache中以ISAPI方式来安装PHP,原因是到目前为止,Apache通过mod_isapi.so模块来实现的ISAPI功能并不完整,并未完整实现微软对ISAPI所制定的全部规范。

同样的,由于以ISAPI方式来安装PHP时,PHP也没有独立的进程,也是作为模块被加载到Apache中的,因此,同样也需要使用PHP的线程安全版本。

如果你要在Apache中以FastCGI方式使用PHP,则同在IIS中使用FastCGI的PHP的情况一样,你应该使用PHP的非线程安全的版本。原因是在Apache中以FastCGI方式安装PHP时,PHP拥有独立的进程,并且FastCGI是单一线程的,故应使用PHP的非线程安全版本以提高性能。


15、线程与进程的区别


这么解释问题吧:

1。单进程单线程:一个人在一个桌子上吃菜。
2。单进程多线程:多个人在同一个桌子上一起吃菜。
3。多进程单线程:多个人每个人在自己的桌子上吃菜。

多线程的问题是多个人同时吃一道菜的时候容易发生争抢,例如两个人同时夹一个菜,一个人刚伸出筷子,结果伸到的时候已经被夹走菜了。。。此时就必须等一个人夹一口之后,在还给另外一个人夹菜,也就是说资源共享就会发生冲突争抢。


1。对于 Windows 系统来说,【开桌子】的开销很大,因此 Windows 鼓励大家在一个桌子上吃菜。因此 Windows 多线程学习重点是要大量面对资源争抢与同步方面的问题。


2。对于 Linux 系统来说,【开桌子】的开销很小,因此 Linux 鼓励大家尽量每个人都开自己的桌子吃菜。这带来新的问题是:坐在两张不同的桌子上,说话不方便。因此,Linux 下的学习重点大家要学习进程间通讯的方法。

--
补充:有人对这个开桌子的开销很有兴趣。我把这个问题推广说开一下。

开桌子的意思是指创建进程。开销这里主要指的是时间开销。
可以做个实验:创建一个进程,在进程中往内存写若干数据,然后读出该数据,然后退出。此过程重复 1000 次,相当于创建/销毁进程 1000 次。在我机器上的测试结果是:
UbuntuLinux:耗时 0.8 秒
Windows7:耗时 79.8 秒
两者开销大约相差一百倍。

这意味着,在 Windows 中,进程创建的开销不容忽视。换句话说就是,Windows 编程中不建议你创建进程,如果你的程序架构需要大量创建进程,那么最好是切换到 Linux 系统。

大量创建进程的典型例子有两个,一个是 gnu autotools 工具链,用于编译很多开源代码的,他们在 Windows 下编译速度会很慢,因此软件开发人员最好是避免使用 Windows。另一个是服务器,某些服务器框架依靠大量创建进程来干活,甚至是对每个用户请求就创建一个进程,这些服务器在 Windows 下运行的效率就会很差。这"可能"也是放眼全世界范围,Linux 服务器远远多于 Windows 服务器的原因。

--
再次补充:如果你是写服务器端应用的,其实在现在的网络服务模型下,开桌子的开销是可以忽略不计的,因为现在一般流行的是按照 CPU 核心数量开进程或者线程,开完之后在数量上一直保持,进程与线程内部使用协程或者异步通信来处理多个并发连接,因而开进程与开线程的开销可以忽略了。

另外一种新的开销被提上日程:核心切换开销。

现代的体系,一般 CPU 会有多个核心,而多个核心可以同时运行多个不同的线程或者进程。

当每个 CPU 核心运行一个进程的时候,由于每个进程的资源都独立,所以 CPU 核心之间切换的时候无需考虑上下文。

当每个 CPU 核心运行一个线程的时候,由于每个线程需要共享资源,所以这些资源必须从 CPU 的一个核心被复制到另外一个核心,才能继续运算,这占用了额外的开销。换句话说,在 CPU 为多核的情况下,多线程在性能上不如多进程。

因而,当前面向多核的服务器端编程中,需要习惯多进程而非多线程。 

--------------------------------------------------------------------------------------------------------

很多人答案说的是操作系统提供的多进程而不是单个程序内的多线程。

多线程使得程序内部可以分出多个线程来做多件事情,而不会造成程序界面卡死。比如迅雷等多线程下载工具就是典型的多线程。一个下载任务进来,迅雷把文件平分成10份,然后开10个线程分别下载。这时主界面是一个单独的线程,并不会因为下载文件而卡死。而且主线程可以控制下属线程,比如某个线程下载缓慢甚至停止,主线程可以把它强行关掉并重启另外一个线程。

另外就是一些程序的打印功能,比如记事本、Adobe Reader,打印的时候就只能打印,无法在主界面进行操作,而Word就有“后台打印”的功能,点了打印命令之后,还可以回到主界面进行修改、保存等操作。

另外多线程除了并行完成一些任务以外,还有生产者-消费者模式。比如Windows命令行下在某个硬盘根目录执行一个"dir/s | more"命令,前一条显示硬盘里的所有文件,要执行很久才能执行得完,后面那条命令会把前面命令的输出分屏显示出来。但是执行整条命令时,会立刻有显示,也就是说,前面一条命令输出满一页内容到缓冲区,more命令就把缓冲区封死了,等用户敲了一个键显示下一屏的时候,more命令把缓冲区的内容取出并清空,前面的命令才能输出下一屏到缓冲区。这样的多线程使得整条命令不用等待前面的命令全部执行完才能执行下一条命令。

多线程和多进程的区别。平常指的多进程是操作系统下同时运行多个进程,比如Word和Excel同时打开,并且可以并行地同时执行一些操作。这种多进程和多线程没什么好比较的。可以比较的是同一个程序里的多线程和多进程。

多线程因为在同一个进程里,所以可以共享内存和其他资源,比如迅雷里10个线程一齐下载一个文件,这个文件是由进程打开的,然后10个线程都可以往里写入东西。如果是10个进程就不行了,操作系统不允许一个文件由两个进程同时写入。另外,Chrome就是一个典型的多进程程序,里面每个标签页、扩展、插件都是单独的进程,各自独占资源,相互隔离,一个进程出错死掉只会影响一个页面或者插件,再也不会出现Flash插件出错崩溃导致整个浏览器崩溃的情况了。

--------------------------------------------------------------------------------------------------------

多线程最一般的应用是简化程序的写法。比如对于IO工作而言,如果底层的API仅提供了同步操作函数,这样一来,单线程的应用很难写,但是支持多线程后,完全可以把IO操作转入线程中执行,主线程仍然可以进行正常的消息循环。

举一个小例子。你用qq邮箱的中转站功能,上传一个文件,在文件传输的时候,你可以最小化上传窗口,去发送、查看邮件,这个就是典型的多线程例子。

打个比方:我们寝室洗手台有2个水龙头,就是多线程。可以满足2个人刷牙。不然排队蛋疼。

举个最最最简单的例子,程序里面要干两件毫不相干的事情A和B,各费时10分钟,那单线程程序就会这样:等A完了再执行B,总共耗时20分钟。但如果将A和B用两个线程来做则如下:等待两个线程结束。如果两个线程被分配到不同的cpu上,则理论耗时共10分钟。

--------------------------------------------------------------------------------------------------------

同样以吃饭举例子,假设很多人需要吃饭吃饭,桌子不够,那么桌子就是紧缺资源。单线程就是整个餐厅只有一个单人桌,这个人吃完了,下一个人轮上。但大餐馆用的可能是八仙桌(好吧我比较喜欢这种古代的方正桌子),同时能容纳八个人吃饭,这就是多线程:从一次一个变成了一次多个或者多次多个。如何安排吃饭顺序对程序员来说是个巨大的挑战,主要问题在于对资源-桌子的安排上,举例说明:

1.死锁-大家都在等着吃饭,但需要特定条件才能开吃:桌一等着桌二的碗,桌二等着桌三的筷子,桌三呢,等着桌一的勺。于是这三桌就都不能吃饭。

2.非原子变量更新-空闲桌子的数字是要加以保护的,类似于特定人员才能修改,假如没有保护,空桌子一会儿是四个,一会儿是五个,最终的数字往往是不正确的,服务员要被玩死-这就是多线程的加锁操作。

还有其他几种,就不多说了,多线程的调度和使用是一个坑,是否能用好是程序员水平高低的一个标志。 


16、FastCgi与PHP-fpm之间是个什么样的关系

 

我在网上查fastcgi与php-fpm的关系,查了快一周了,基本看了个遍,真是众说纷纭,没一个权威性的定义。

网上有的说,fastcgi是一个协议,php-fpm实现了这个协议; 有的说,php-fpm是fastcgi进程的管理器,用来管理fastcgi进程的; 有的说,php-fpm是php内核的一个补丁; 有的说,修改了php.ini配置文件后,没办法平滑重启,所以就诞生了php-fpm; 还有的说PHP-CGI是PHP自带的FastCGI管理器,那这样的话干吗又弄个php-fpm出来,我就更晕了;

发个贴,想听听大家的理解,网上的我都已经看了个遍,因为我查了一周了,哈哈,所以想听听原创的理解。
-------------------------------------------------------------------------------------------------
刚开始对这个问题我也挺纠结的,看了《HTTP权威指南》后,感觉清晰了不少。

首先,CGI是干嘛的?CGI是为了保证web server传递过来的数据是标准格式的,方便CGI程序的编写者。

    web server(比如说nginx)只是内容的分发者。比如,如果请求/index.html,那么web server会去文件系统中找到这个文件,发送给浏览器,这里分发的是静态数据。好了,如果现在请求的是/index.php,根据配置文件,nginx知道这个不是静态文件,需要去找PHP解析器来处理,那么他会把这个请求简单处理后交给PHP解析器。Nginx会传哪些数据给PHP解析器呢?url要有吧,查询字符串也得有吧,POST数据也要有,HTTP header不能少吧,好的,CGI就是规定要传哪些数据、以什么样的格式传递给后方处理这个请求的协议。仔细想想,你在PHP代码中使用的用户从哪里来的。

    当web server收到/index.php这个请求后,会启动对应的CGI程序,这里就是PHP的解析器。接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以规定CGI规定的格式返回处理后的结果,退出进程。web server再把结果返回给浏览器。

好了,CGI是个协议,跟进程什么的没关系。那fastcgi又是什么呢?Fastcgi是用来提高CGI程序性能的。

    提高性能,那么CGI程序的性能问题在哪呢?"PHP解析器会解析php.ini文件,初始化执行环境",就是这里了。标准的CGI对每个请求都会执行这些步骤(不闲累啊!启动进程很累的说!),所以处理每个时间的时间会比较长。这明显不合理嘛!那么Fastcgi是怎么做的呢?首先,Fastcgi会先启一个master,解析配置文件,初始化执行环境,然后再启动多个worker。当请求过来时,master会传递给一个worker,然后立即可以接受下一个请求。这样就避免了重复的劳动,效率自然是高。而且当worker不够用时,master可以根据配置预先启动几个worker等着;当然空闲worker太多时,也会停掉一些,这样就提高了性能,也节约了资源。这就是fastcgi的对进程的管理。

那PHP-FPM又是什么呢?是一个实现了Fastcgi的程序,被PHP官方收了。

    大家都知道,PHP的解释器是php-cgi。php-cgi只是个CGI程序,他自己本身只能解析请求,返回结果,不会进程管理(皇上,臣妾真的做不到啊!)所以就出现了一些能够调度php-cgi进程的程序,比如说由lighthttpd分离出来的spawn-fcgi。好了PHP-FPM也是这么个东东,在长时间的发展后,逐渐得到了大家的认可(要知道,前几年大家可是抱怨PHP-FPM稳定性太差的),也越来越流行。

好了,最后来回来你的问题。
网上有的说,fastcgi是一个协议,php-fpm实现了这个协议

    对。

有的说,php-fpm是fastcgi进程的管理器,用来管理fastcgi进程的

    对。php-fpm的管理对象是php-cgi。但不能说php-fpm是fastcgi进程的管理器,因为前面说了fastcgi是个协议,似乎没有这么个进程存在,就算存在php-fpm也管理不了他(至少目前是)。 有的说,php-fpm是php内核的一个补丁

    以前是对的。因为最开始的时候php-fpm没有包含在PHP内核里面,要使用这个功能,需要找到与源码版本相同的php-fpm对内核打补丁,然后再编译。后来PHP内核集成了PHP-FPM之后就方便多了,使用--enalbe-fpm这个编译参数即可。

有的说,修改了php.ini配置文件后,没办法平滑重启,所以就诞生了php-fpm

    是的,修改php.ini之后,php-cgi进程的确是没办法平滑重启的。php-fpm对此的处理机制是新的worker用新的配置,已经存在的worker处理完手上的活就可以歇着了,通过这种机制来平滑过度。

还有的说PHP-CGI是PHP自带的FastCGI管理器,那这样的话干吗又弄个php-fpm出

    不对。php-cgi只是解释PHP脚本的程序而已。
----------------------------------------------------------------------------------------------
你(PHP)去和爱斯基摩人(web服务器,如 Apache、Nginx)谈生意

你说中文(PHP代码),他说爱斯基摩语(C代码),互相听不懂,怎么办?那就都把各自说的话转换成英语(FastCGI 协议)吧。

怎么转换呢?你就要使用一个翻译机(PHP-FPM)
(当然对方也有一个翻译机,那个是他自带的)

我们这个翻译机是最新型的,老式的那个(PHP-CGI)被淘汰了。不过它(PHP-FPM)只有年轻人(Linux系统)会用,老头子们(Windows系统)不会摆弄它,只好继续用老式的那个。
---------------------------------------------------------------------------------------------
Fastcgi是CGI的升级版,一种语言无关的协议,用来沟通程序(如PHP, Python, Java)和Web服务器(Apache2, Nginx), 理论上任何语言编写的程序都可以通过Fastcgi来提供Web服务。
Fastcgi的特点是会在一个进程中依次完成多个请求,以达到提高效率的目的,大多数Fastcgi实现都会维护一个进程池。

而PHP-fpm就是针对于PHP的,Fastcgi的一种实现,他负责管理一个进程池,来处理来自Web服务器的请求。目前,PHP-fpm是内置于PHP的。

但是PHP-fpm仅仅是个“PHP Fastcgi 进程管理器”, 它仍会调用PHP解释器本身来处理请求,PHP解释器(在Windows下)就是php-cgi.exe.
---------------------------------------------------------------------------------------------
FASTCGI:WEB服务器与处理程序之间通信的一种协议,是CGI的改进方案。

CGI程序反复加载是CGI性能低下的主要原因,如果CGI程序保持在内存中并接受FastCGI进程管理器调度,则可以提供良好的性能、伸缩性、Fail-Over特性等。

FASTCGI是常驻型的CGI,它可以一直运行,在请求到达时,不会花费时间去fork一个进程来处理。

FastCGI是语言无关的、可伸缩架构的CGI开放扩展,将CGI解释器进程保持在内存中,以此获得较高的性能。

一般情况下,FastCGI的整个工作流程是这样的:

1、Web Server启动时载入FastCGI进程管理器(IIS ISAPI或Apache Module)

2、FastCGI进程管理器自身初始化,启动多个CGI解释器进程(可见多个php-cgi)并等待WebServer的连接。

3、当客户端请求到达Web Server时,FastCGI进程管理器选择并连接到一个CGI解释器。 Web server将CGI环境变量和标准输入发送到FastCGI子进程php-cgi。

4、FastCGI子进程完成处理后将标准输出和错误信息从同一连接返回Web Server。当FastCGI子进程关闭连接时,请求便告处理完成。FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在Web Server中)的下一个连接。在CGI模式中,php-cgi在此便退出了。
-------------------------------------------------------------------------------------------
fastcgi 是 app server 和web server 之间的通信协议。 正常架构 app server 是master,web server是client

php-fpm 带两个功能:1.实现了一个支持fastcgi协议的server程序 2. 进程管理器

有了php-fpm,就可以把php脚本变成 多进程模式,采用fastcgi协议的app server,和web server进行通信
-------------------------------------------------------------------------------------------
CGI => http://www.w3.org/CGI/
FastCGI => http://www.fastcgi.com/drupal/
FPM => FastCGI Process Manager(FastCGI进程管理器)
-------------------------------------------------------------------------------------------
fastcgi是一个协议,没错。php-fpm实现了这个协议,没错。 php-fpm的fastcgi协议需要有个金城吧?php-fpm实现的fastcgi进程就叫php-cgi= =。所以php-fpm就是他自身的fastcgi或php-cgi进程管理器,没错。php-fpm在5.2之前官方是没有php-fpm的。php-fpm是作为一个第三方的补丁你才能用的。5.2之后捏,官方就已经默认加入了,从此就不是一个补丁了。
-------------------------------------------------------------------------------------------
17、PHP的线程安全模式(Thread Safety)

PHP自身是不支持线程的,但是它在安装的时候,涉及到一个线程安全的问题,Windows下提供了二种安装包,Linux下编译安装提供了–enable-maintainer-zts这个选项。
很多人一看到“安全”,就以为是好事,其实不然。
既然PHP没有线程,那么这个线程安全指的是什么呢?这和它的运行方式有关。
这里仅对Linux系统下流行的两种PHP运行方式(LNMP和LNAMP)来讲一下。
如果是LNMP环境,也就是说PHP以php-fpm方式运行,那么就不涉及到线程安全这个问题了,因为php-fpm是以多进程的方式来运行的。
如果是以LNAMP或者LAMP方式运行(mod_php),那么应该先了解下Apache的MPM,简单点说,Apache支持以多线程的方式运行(Worker),也支持以多进程的方式运行(Prefork)。一般来讲,Linux下的Apache绝大多数都是运行在Prefork模式下,这是出于稳定性的考虑。
所以总结下,LNMP下不需线程安全,Apache下为了稳定性的考虑,建议以多进程的方式运行(Prefork),这样也是不需要线程安全的。
最后一点,PHP安装为线程安全,会比非线程安全多占用一些CPU,并且可能会增加bug或者不稳定的问题,这才是重点,不然PHP就没必要设置这个选项了。

PHP常见面试题汇总