PHP 发送 HTTP 请求方式

在 PHP 中我们发送 https 请求常用的是 curl,今天接触到其他几种方式,这里做下简单的记录。

curl 方式
这种是最常用的方式,我这里就不做记录了,平时开发中用的最多了。

stream 流的方式
我们可以使用 file_get_contents 的第三个参数来传递一个包装的 https stream,下面来个简单的代码:

  $opts = array(
      'https' => array(
          'method'  => "GET",
          'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
       )
   );
  $context = stream_context_create($opts);
  $result = file_get_contents("https://www.baidu.com", false, $context);
 var_dump($result);

详细信息请阅读官方文档

socket方式

/*
参数说明:
hostname: 主机名
port: 端口
errNo: 错误码
errMsg: 错误信息
timeout: 设置连接的时限,单位为秒
*/
$fp = fsockopen("www.baidu.com", 80, $errNo, $errMsg, 10);
if (!$fp) {
    echo $errNo, "<br />", $errMsg;
} else {
    $out = "GET / HTTP/1.1\r\n";
    $out .= "Host: www.baidu.com\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 128);
    }
    fclose($fp);
}

上面只是一个简单的例子,具体用法详细参考官方文档

数据库持久连接

持久的数据库连接是指在脚本结束运行时不关闭的连接。当收到一个持久连接的请求时。PHP 将检查是否已经存在一个(前面已经开启的)相同的持久连接。如果存在,将直接使用这个连接;如果不存在,则建立一个新的连接。所谓“相同”的连接是指用相同的用户名和密码到相同主机的连接。

对 web 服务器的工作和分布负载没有完全理解的读者可能会错误地理解持久连接的作用。特别的,持久连接不会在相同的连接上提供建立“用户会话”的能力,也不提供有效建立事务的能力。实际上,从严格意义上来讲,持久连接不会提供任何非持久连接无法提供的特殊功能。

为什么?

这和 web 服务器工作的方式有关。web 服务器可以用三种方法来利用 PHP 生成 web 页面。

第一种方法是将 PHP 用作一个单独运行的语言解释器(CGI Wapper)。以这种方法运行,PHP 会为向 web 服务器提出的每个 PHP 页面请求生成并结束一个 PHP解释器线程。由于该线程会随每个请求的结束而结束,因此任何在这个线程中利用的任何资源(例如指向SQL 数据库服务器的连接)都会随线程的结束而关闭。在这种情况下,使用持久连接和非持久连接没有任何区别——因为PHP脚本本身的执行不是持久的。

第二,也是最常用的方法,是把 PHP 用作多进程 web 服务器的一个模块,这种方法目前只适用于 Apache。对于一个多进程的服务器,其典型特征是有一个父进程和一组子进程协调运行,其中实际生成 web 页面的是子进程。每当客户端向父进程提出请求时,该请求会被传递给还没有被其它的客户端请求占用的子进程。这也就是说当相同的客户端第二次向服务端提出请求时,它将有可能被一个不同的子进程来处理。在开启了一个持久连接后,所有请求 SQL 服务的后继页面都能够重用这个已经建立的 SQL Server 连接。

最后一种方法是将 PHP 用作多线程 web 服务器的一个插件。目前 PHP 4 已经支持 ISAPI、WSAPI 和 NSAPI(在非Windows 环境下),这些使得 PHP 可以被用作诸如 Netscape FastTrack (iPlanet)、Microsoft's Internet Information Server (IIS) 和 O'Reilly's WebSite Pro 等多线程 web 服务器的插件。持久连接的行为和前面所描述的多过程模型在本质上是相同的。注意 PHP 3 不支持 SAPI。

如果持久连接并没有任何附加的功能,那么使用它有什么好处?

答案非常简单——效率。当Web Server创建到SQL服务器的连接耗费(Overhead)较高(如耗时较久,消耗临时内存较多)时,持久连接将更加高效。Overhead高低取决于很多因素。例如,数据库的种类,数据库服务和web 服务是否在同一台服务器上,SQL 服务器负载状况等。当Overhead较高,每次创建数据库连接成本较高时,持久连接将显著的提高效率。它使得每个子进程在其生命周期中只做一次连接操作,而非每次在处理一个页面时都要向SQL 服务器提出连接请求。这也就是说,每个子进程将对服务器建立各自独立的持久连接。例如,如果有 20 个不同的子进程运行某脚本建立了持久的 SQL 服务器持久连接,那么实际上向该 SQL 服务器建立了 20 个不同的持久连接,每个进程占有一个。

注意,如果持久连接的子进程数目超过了设定的数据库连接数限制,系统将会产生一些问题。如果数据库的同时连接数限制为 16,而在繁忙会话的情况下,有 17 个线程试图连接,那么有一个线程将无法连接。如果这个时候,在脚本中出现了使得连接无法关闭的错误(例如无限循环),则该数据库的 16 个连接将迅速地受到影响。请查阅使用的数据库的文档,以获取关于如何处理已放弃的及闲置的连接的方法。

Warning
在使用持久连接时还有一些特别的问题需要注意。例如在持久连接中使用数据表锁时,如果脚本不管什么原因无法释放该数据表锁,其随后使用相同连接的脚本将会被持久的阻塞,使得需要重新启动 httpsd 服务或者数据库服务。另外,在使用事务处理时,如果脚本在事务阻塞产生前结束,则该阻塞也会影响到使用相同连接的下一个脚本。不管在什么情况下,都可以通过使用 register_shutdown_function() 函数来注册一个简单的清理函数来打开数据表锁,或者回滚事务。或者更好的处理方法,是不在使用数据表锁或者事务处理的脚本中使用持久连接,这可以从根本上解决这个问题(当然还可以在其它地方使用持久连接)。

Nginx(PHP/fastcgi)的PATH_INFO问题

PATH_INFO是一个CGI 1.1的标准,经常用来做为传参载体.
比如, 我们可以使用PATH_INFO来代替Rewrite来实现伪静态页面, 另外不少PHP框架也使用PATH_INFO来作为路由载体.

在Apache中, 当不加配置的时候, 对于PHP脚本, AcceptPathInfo是默认接受的, 也就是说:如果在服务器在存在一个/laruence/index.php那么, 对于如下请求,
/laruence/index.php/dummy
/laruence/dummy
Apache都接受, 都会认为是对info.php的访问, 并会设置PATH_INFO为dummy
而对于Nginx下, 是不支持PATH INFO的, 也就是它不会默认设置PATH_INFO.
而因为默认的配置文件对PHP的支持只是很基础的, 所以对于默认配置来说对于上面的访问也会是404, 提示找不到文件出错.
这对于一些使用PATH_INFO来传递关键信息的PHP框架来说(比如Kohana, Thinkphp), 简直是致命的.
对于这个问题, 一般来说有俩种解决方法, 第一种就是使用rewrite, 但是这个方法的缺点也是很明显的, 需要把PATH_INFO转换成Query String. 此处就不说明这种方法了~
而, 第二种方法就是我今天要提的, 模拟PATH_INFO:

首先 , 我们知道在Nginx中, 是通过对文件名的扩展名匹配, 来决定是否要交给php cgi服务器去解释的. 在nginx.conf中一般都有如下的默认配置段:

  location ~ .php$ {
       fastcgi_index  index.php;
       fastcgi_pass   127.0.0.1:9000;
       include        fastcgi_params;
  }

所以,对于形如/laruence/info.php/pathinfo这样的文件路径, Nginx是不会正确的交给php cgi服务器的. 所以我们需要改写这段配置为:

       location ~ .php {//片段匹配
             fastcgi_index  index.php;
             fastcgi_pass   127.0.0.1:9000;
             include        fastcgi_params;
       }

现在, 脚本路径已经交由PHP自己处理了. 那怎么增加PATH_INFO呢?

首先, 我们需要打开PHP中cgi.fix_pathinfo配置项, 打开这个配置项以后, PHP会去根据CGI规范来检查SCRIPT_FILENAME中那部分是访问脚本和PATH_INFO(ini配置解释), 并根据SCRIPT_NAME来修改PATH_INFO(和PATH_TRANSLATED)为正确的值(其实也就是说明, PHP最初对CGI 1.1的支持并不到位)然后, 就只要添加一个FASTCGI_PARAM项就好了:

  location ~ .php {
        fastcgi_index  index.php;
        fastcgi_pass   127.0.0.1:9000;
        include        fastcgi_params;
        fastcgi_param  PATH_INFO $fastcgi_script_name;
  }

当然, 上面的解决方法, 把对路径的分析交给了PHP去处理, 网上也有朋友给出了另外一种配置方法, 这个方法是由Nginx来分析路径(也就不需要fix_pathinfo):

  location ~ \.php {
       fastcgi_index index.php;
       fastcgi_pass 127.0.0.1:9000;
       include      fastcgi_params;
       set $path_info "";
       set $real_script_name $fastcgi_script_name;
       if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
             set $real_script_name $1;
             set $path_info $2;
       }
       fastcgi_param SCRIPT_FILENAME /var/html/$real_script_name;
       fastcgi_param SCRIPT_NAME $real_script_name;
       fastcgi_param PATH_INFO $path_info;
   }

后记, 最近发现的一个安全漏洞(Nginx + PHP CGI的一个可能的安全漏洞)和这个配置有关系, 请大家务必在使用第二种配置的时候,关闭cgi.fix_pathinfo.

Nginx配置文件nginx.conf中文详解

这篇是目前最完整的Nginx配置参数中文说明了。更详细的模块参数请参考:https://wiki.nginx.org/Main

定义Nginx运行的用户和用户组

user www www;

nginx进程数,建议设置为等于CPU总核心数。

worker_processes 8;

全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]

error_log /var/log/nginx/error.log info;

进程文件

pid /var/run/nginx.pid;

一个nginx进程打开的最多文件描述符数目
理论值应该是最多打开文件数(系统的值ulimit -n)与nginx进程数相除,但是nginx分配请求并不均匀,所以建议与ulimit -n的值保持一致。

worker_rlimit_nofile 65535;

工作模式与连接数上限

events
{
参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll模型是Linux 2.6以上版本内核中的高性能网络I/O模型,如果跑在FreeBSD上面,就用kqueue模型。
use epoll;
单个进程最大连接数(最大连接数=连接数*进程数)
worker_connections 65535;
}

设定https服务器

https
{
include mime.types;                    #文件扩展名与文件类型映射表
default_type application/octet-stream; #默认文件类型
charset utf-8;                         #默认编码
server_names_hash_bucket_size 128;     #服务器名字的hash表大小
client_header_buffer_size 32k;         #上传文件大小限制
large_client_header_buffers 4 64k;     #设定请求缓
client_max_body_size 8m;               #设定请求缓
sendfile on;                           #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。
autoindex on;                          #开启目录列表访问,合适下载服务器,默认关闭。
tcp_nopush on;                         #防止网络阻塞
tcp_nodelay on;                        #防止网络阻塞
keepalive_timeout 120;                 #长连接超时时间,单位是秒

FastCGI相关参数是为了改善网站的性能:减少资源占用,提高访问速度。下面参数看字面意思都能理解。
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;

gzip模块设置
gzip on;               #开启gzip压缩输出
gzip_min_length 1k;    #最小压缩文件大小
gzip_buffers 4 16k;    #压缩缓冲区
gzip_https_version 1.0; #压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
gzip_comp_level 2;     #压缩等级
gzip_types text/plain application/x-javascript text/css application/xml;
                       #压缩类型,默认就已经包含text/html,所以下面就不用再写了,写上去也不会有问题,但是会有一个warn。
gzip_vary on;
limit_zone crawler $binary_remote_addr 10m; #开启限制IP连接数的时候需要使用

upstream的负载均衡,weight是权重,可以根据机器配置定义权重。weigth参数表示权值,权值越高被分配到的几率越大。
upstream blog.ha97.com {
    server 192.168.80.121:80 weight=3;
    server 192.168.80.122:80 weight=2;
    server 192.168.80.123:80 weight=3;
}

虚拟主机的配置

server
{

listen      80;                   #监听端口
server_name www.ha97.com ha97.com;#域名可以有多个,用空格隔开
index index.html index.htm index.php;
root /data/www/ha97;
location ~ .*\.(php|php5)?${
fastcgi_pass 127.0.0.1:9000;
     fastcgi_index index.php;
     include fastcgi.conf;
}

location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)${ 
     expires 10d;     #图片缓存时间设置
}

location ~ .*\.(js|css)?${
     expires 1h;           #JS和CSS缓存时间设置
}
log_format access '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$https_referer" '
'"$https_user_agent" $https_x_forwarded_for';
                  #定义本虚拟主机的访问日志
                  #日志格式设定
access_log /var/log/nginx/ha97access.log access;

对 "/" 启用反向代理
location / {
      proxy_pass https://127.0.0.1:88;
      proxy_redirect off;
      proxy_set_header X-Real-IP $remote_addr; #后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
(以下是一些反向代理的配置,可选。)
proxy_set_header Host $host;
client_max_body_size 10m;      #允许客户端请求的最大单文件字节数
client_body_buffer_size 128k;  #缓冲区代理缓冲用户端请求的最大字节数,
proxy_connect_timeout 90;      #nginx跟后端服务器连接超时时间(代理连接超时)
proxy_send_timeout 90;         #后端服务器数据回传时间(代理发送超时)
proxy_read_timeout 90;         #连接成功后,后端服务器响应时间(代理接收超时)
proxy_buffer_size 4k;          #设置代理服务器(nginx)保存用户头信息的缓冲区大小
proxy_buffers 4 32k;           #proxy_buffers缓冲区,网页平均在32k以下的设置
proxy_busy_buffers_size 64k;   #高负荷下缓冲大小(proxy_buffers*2)
proxy_temp_file_write_size 64k;#设定缓存文件夹大小,大于这个值,将从upstream服务器传

}


location /NginxStatus { #设定查看Nginx状态的地址
      stub_status on;
      access_log on;
      auth_basic "NginxStatus";
      auth_basic_user_file conf/htpasswd;#htpasswd文件的内容可以用apache提供的htpasswd工具来产生。
}

本地动静分离反向代理配置;所有jsp的页面均交由tomcat或resin处理
location ~ .(jsp|jspx|do)?$ {
     proxy_set_header Host $host;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_pass https://127.0.0.1:8080;
    }

所有静态文件由nginx直接读取不经过tomcat或resin
location ~ .*.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)${ expires 15d; 
}

location ~ .*.(js|css)?${ expires 1h; }
}
}

PHP数组函数总结

==========================

PHP的数组

==========================

一、 基本概念

  1. 数组的概念:

*2. 数组的分类:根据数组的下标(索引值)类型不同分为:

      索引式数组:数组下标都是整数的, 默认数组的索引下标是从0开始
      关联式数组:数组下标是以字串表示的 (在其他强类型语言中有的称这个叫集合)

3.php中数组的定义:

   *(1). 直接赋值: $数组名[下标]="值";
           echo "<p>==============索引式数组=================<p/>";
           $a[]=10;
           $a[]=20;
           $a[]=30;
           $a[10]="cc";
           $a[]="bb";
  
           echo "<p>==============关联式数组==================<p/>";
           $p["name"]="张三";
           $p["sex"]="男";
           $p["age"]=20;
           $p["name"]="李四"; //修改
          
   *(2). 使用array语言结构来定义。
           $a = array(10,20,30,40);   //使用array函数定义一个数组(未指定下标)
           $b = array("aa","bb","cc");//使用array函数定义一个数组(未指定下标)
           $c = array(1=>10,2=>20,6=>80);     //使用array函数,定义一个指定下标的数组

           $stu = array("name"=>"张三","age"=>20); //使用array函数,定义一个指定关联下标的数组

           $stu = array("name"=>"张三","age"=>20,3=>"qq","pp"); //这是pp的下标是4
           $p = array("name"=>"张三","age"=>20,"aa","bb"); //这时数组aa和bb的下标为0,1

   (3)、 使用快捷方式定义数组。(>=PHP5.3.0)
        $a = [10,20,30];
        $stu = ["name"=>"张三","age"=>20];
       

 4. 多维数组:当一个数组中的元素单元还是一个数组时,称为多维数组。(几层就是几维)
      如:echo $a[10]; //一维数组
           echo $a[1][2]; //二维数组
          
           $a = array(10,20,40,50); //一维数组
     
           $b = array( array(10,20),array(30,40)...); //二维数组
          
      如二维数组的赋值:
           直接赋值:$a[][]=10;
               

二、数组的遍历(迭代)输出(4种)

 1.使用循环结构:for/while/do...while
      使用到一个函数:count()--获取数组的长度(元素的个数)
      示例:
           $a=array(10,20,30,40,50,60);
           $ll=count($a);
           for($i=0;$i<$ll;$i++){
                echo $a[$i]." ";
           }
      注意:使用for循环只适合输出索引式数组,并且数组下标是从0开始连续的整数。
      但是可以使用array_values()函数来弥补数组下标不足的地方。
      示例:
           echo "<br/>";
           //输出非规则的数组
           $a=array(2=>10,3=>40,6=>80,4=>30,"name"=>"张三");
           //获取数组中的所有值,并以索引式数组返回
           $list = array_values($a);
           //使用for循环遍历
           for($i=0;$i<count($list);$i++){
                echo $list[$i]." ";
           }

 *2. 使用foreach遍历数组
           格式:foreach(被遍历的数组 as [键=>]值){
                          ....
                  }

           示例://关联式数组的输出
                $stu = array("name"=>"zhangsan","age"=>20,"sex"=>"man");
                foreach($stu as $k=>$v){
                     echo "{$k}=>{$v}<br/>";
                }
               
 3.  联合while each reset list遍历数组(半自动方式)

      each()--获取当前数组指针位置的键和值,并以关联和索引两种方式返回,并且指针向下移动一位。
      reset()--将数组指针移至首位。
      list()--直接解析赋过来的数组中的索引下标对应的值,解析是从0开始
           list($v1,$v2)=array("key"="name",0=>"name","value"="zhangsan",1=>"zhangsan");
           echo $v1.":".$v2; //name:zhangsan
          
      示例:
           //使用while、each、reset、list解析遍历的结果
           $stu = array("name"=>"zhangsan","age"=>20,"sex"=>"man");
           reset($stu);
           while(list($k,$v)=each($stu)){
                echo "{$k}=>{$v}<br/>";
           }
          
          
 4. 使用下面的函数来遍历数组(纯手工遍历数组)
      reset()-- 将数组的内部指针指向第一个单元
      end() — 将数组的内部指针指向最后一个单元
      next() — 将数组中的内部指针向前移动一位
      prev() — 将数组的内部指针倒回一位
      current() — 返回数组中的当前单元
      key() — 从关联数组中取得键名
     
      do....while循环
     
      示例:
           $a = array(10,20,30,40,50,60);
           reset($a);//将数组指针移至首位
           do{
                echo key($a)."=>".current($a)."<br/>"; //获取键和值
           }while(next($a));

          

三、超全局数组:

  $GLOBALS 包含以下所有信息
 *$_SERVER 服务器和执行环境信息
 *$_GET  通过 URL 参数传递给当前脚本的变量的数组。
 *$_POST 通过 HTTP POST 方法传递给当前脚本的变量的数组。
 *$_FILES 保存文件上传信息(在文件处理章节中细讲)
 (cookie和session在会话跟踪章节中细讲)
 *$_COOKIE 通过 HTTP Cookies 方式传递给当前脚本的变量的数组(用于储存论坛、文库、博客等登陆信息)
 *$_SESSION 当前脚本可用 SESSION 变量的数组。(用于网站购物车等的信息存储)
 *$_REQUEST 包含get、post和cookie
  $_ENV 存储的是系统环境变量信息

 *$_SERVER["HTTP_REFERER"]--上一页面的url地址
  $_SERVER["SERVER_NAME"]--服务器的主机名
 *$_SERVER["SERVER_ADDR"]--服务器端的IP地址
  $_SERVER["SERVER_PORT"]--服务器端的端口
 *$_SERVER["REMOTE_ADDR"]--客户端的IP
  $_SERVER["DOCUMENT_ROOT"]--服务器的web目录路径
 *$_SERVER["REQUEST_URI"];//--URL地址
  echo $_GET["name"];
  echo $_REQUEST["name"]; //获取信息比上面get的会慢一些


  form表单的get提交方式:url地址可见,相对不安全,长度受限,可以做为标签连接使用。
  form表单的post提交方式:url地址不可见,相对安全,长度不受限。

四、 数组的相关函数

 1. 数组的键和值的操作函数
      *array_values — 返回数组中所有的值
       array_keys — 返回数组中所有的键名
       array_flip — 交换数组中的键和值
      *in_array — 检查数组中是否存在某个值
       array_reverse — 返回一个单元顺序相反的数组
      *is_array() --判断是否是数组

 2. 数组的统计相关函数
      *count -- 计算数组中的单元数目或对象中的属性个数
       array_count_values -- 统计数组中所有的值出现的次数
       array_unique -- 移除数组中重复的值

 3. 带回调函数的
       array_filter --  用回调函数过滤数组中的单元     
       array_walk -- 对数组中的每个成员应用用户函数
       array_map --  将回调函数作用到给定数组的单元上

 4. 数组的排序
      *sort -- 对数组排序(升序)
       rsort -- 对数组逆向排序(降序)
       asort -- 对数组进行排序并保持索引关系(关联数组排序)
       arsort --  对数组进行逆向排序并保持索引关系
      *usort --  使用用户自定义的比较函数对数组中的值进行排序
       uasort --  使用用户自定义的比较函数对数组中的值进行排序并保持索引关联
       ksort -- 对数组按照键名排序
       krsort -- 对数组按照键名逆向排序
       uksort --  使用用户自定义的比较函数对数组中的键名进行排序
      *natsort --  用“自然排序”算法对数组排序
       natcasesort --  用“自然排序”算法对数组进行不区分大小写字母的排序
       array_multisort -- 对多个数组或多维数组进行排序(了解)
       array_slice -- 从数组中取出一段
       array_splice --  把数组中的一部分去掉并用其它值取代
       array_combine --  创建一个数组,用一个数组的值作为其键名,另一个数组的值作为其值
      *array_merge -- 合并一个或多个数组
       array_intersect -- 计算数组的交集
       array_diff -- 计算数组的差集