一个 PHP 请求的执行过程

一个 PHP 请求的执行过程

正常请求一个php网站,在浏览器输入网址打开网站,显示网页。但是在整个请求流程中浏览器做什么?服务器又是怎么在后台执行的?接下来就简单解析下一个完整的PHP请求的执行过程。

1、构建请求

浏览器构建请求行信息(如下所示),构建好后,浏览器准备发起网络请求。

例:GET /index.html HTTP1.1

2、查找缓存

在真正发起网络请求之前,浏览器会先在浏览器缓存中查询是否有要请求的文件。当浏览器发现请求资源已经存在浏览器缓存中存有副本,则会拦截请求并返回该资源副本结束请求。如果查找缓存失败,则会进入网络请求,有利于缓解服务器端压力,提升性能。

3、域名解析

浏览器会使用 DNS(将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网)解析找到IP地址。

4、与服务器建立连接

客户端拿到服务器的 ip 地址之后,接下来要做的事情,就是与服务器建立连接,这样才能相互传输数据。

客户端与服务器建立连接需要了解 TCP/IP 协议:本地机和服务器各自有不同的操作系统,仅仅是简单的连到一起完全不能交流信息,因而他们需要定义一些共通的东西来进行交流,TCP/IP就是为此而生。

TCP/IP不是一个协议,而是一个协议族的统称。里面包括了IP协议,IMCP协议,TCP协议,以及我们更加熟悉的http、ftp、pop3协议等等。电脑有了这些,就好像学会了外语一样,就可以和其他的计算机终端做自由的交流了。

TCP是可靠的双向通道,所以需要三次握手和四次挥手。
TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。
状态位:SYN 是发起一个连接,ACK 是回复,RST 是重新连接,FIN 是结束连接。

TCP的三次握手

简单来说:
第一次握手:Client 发送一个 SYN 包到服务器,并进入到 SYN-SEND 状态,等待服务器确认。
第二次握手:Server 接收到 SYN 并返回一个 SYN+ACK 包,此时服务器进入 SYN-RECV 状态。
第三次握手:Client再发送一个 ACK 包,此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

5、发起HTTP请求

浏览器向服务器发送请求行,它包括了请求方法、请求 URI(Uniform Resource Identifier)和 HTTP 版本协议。

例:GET /index.html HTTP1.1

在浏览器发送请求行命令之后,还要以请求头形式发送其他一些信息,把浏览器的一些基础信息告诉服务器。比如包含了浏览器所使用的操作系统、浏览器内核等信息,以及当前请求的域名信息、Cookie等。

其中请求方式有GET,POST,PUT,DELETE 等,其中常用的 POST 会用于发送一些数据给服务器,比如登录网站把用户信息发送给服务器,一般这些数据会通过请求体发送。

请求方法
方法描述
GET获取资源
POST新增资源
PUT更新资源
DELETE删除资源
HEAD获取 HEAD 元数据
CONNECT建立 Tunnel 隧道
OPTIONS获取服务器支持访问资源的方法
TRACE回显服务器收到的请求,可以定位问题
请求头

以下是部分请求头参数:

参数描述
Accept浏览器支持的资源文件类型,如text/html
Accept-Charset客户端可以接受的字符集,防止传过来的是另外的字符集,从而导致出现乱码。
Accept-Encodeing浏览器可以支持的压缩方式
Accept-Language浏览器可以读取的语言
Connection:keep-alive告诉服务器保持长连接,避免重复三次握手建立tcp连接
Content-Type正文格式,设置请求时正文的格式。
Cookie浏览器端的Cookie信息
Cache-Control浏览器端的缓存策略是强缓存
User-Agent浏览器内核和操作系统等信息
Host当前请求的主机域名信息
请求体

POST方式可以将一个页面表单中的组件值通过 “param1=value1&m2=value2” 的键值对形式编码成一个格式化串,通过把数据放在请求体重提交到服务器。也可以通过GET方式拼接参数:“/index.html? param1=value1&m2=value2”。

请求流程

1、HTTP协议基于TCP协议,使用面向连接的方式发送请求,通过stream二进制流的方式传给对方。到了 TCP 层,它会把二进制流变成一个的报文段发送给服务器。
2、在发送每段报文的时候,都需要对方发一个回应ACK来保证报文是否到达对方。如果没有回应,TCP会重新进行传输,直到到达。所以同一个包有可能被传输很多次,但是只是TCP层一直在运作,HTTP不需要做任何处理。
3、TCP 层发送每一个报文的时候,都需要加上自己的地址(即源地址)和它想要去的地方(即目标地址),将这两个信息放到 IP 头里面,交给 IP 层进行传输。
4、IP 层需要查看目标地址和自己是否是在同一个局域网。如果是,就发送 ARP 协议来请求这个目标地址对应的 MAC 地址,然后将源 MAC 和目标 MAC 放入 MAC 头,发送出去即可;如果不在同一个局域网,就需要发送到网关,还要需要发送 ARP 协议,来获取网关的MAC 地址,然后将源 MAC 和网关 MAC 放入 MAC 头,发送出去。
5、网关收到包发现MAC符合,取出目标IP地址,根据路由协议找到下一跳的路由器,获取下一跳路由器的 MAC 地址,将包发给下一跳路由器。
6、路由器一跳一跳终于到达目标的局域网。这个时候,最后一跳的路由器能够发现,目标地址就在自己的某一个出口的局域网上。于是,在这个局域网上发送 ARP,获得这个目标地址的 MAC 地址,将包发出去。
7、目标的机器发现 MAC 地址符合,就将包收起来;发现 IP 地址符合,根据 IP 头中协议项,知道自己上一层是 TCP 协议,于是解析 TCP 的头,里面有序列号,需要看一看这个序列包是不是我要的,如果是就放入缓存中然后返回一个 ACK,如果不是就丢弃。
8、TCP 头里面还有端口号,HTTP 的服务器正在监听这个端口号。于是,目标机器自然知道是
HTTP 服务器这个进程想要这个包,于是将包发给 HTTP 服务器。HTTP 服务器的进程看
到,原来这个请求是要访问一个网页,于是就把这个网页发给客户端。

本段参考PHP完整请求流程

6、服务器处理请求

上面三次握手成功后,服务器开始处理请求。

这里以 Nginx + PHP 为例讲解运行原理,在讲解之前需要先知道一些基础知识。

Nginx
Nginx ("engine x") 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。
Php-fpm
Php-fpm(Php-Fastcgi Process Manager)是 FastCGI 的实现,并提供了进程管理的功能。
而 FastCGI 协议的解析是通过 php-fcgi 进行解析,之后再将脚本交给 php_execute_script 函数运行。

进程包含 master 进程和 worker 进程两种进程:
	master 进程只有一个,负责监听端口,接收来自 Web Server 的请求;
	worker 进程则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个 PHP 解释器,是 PHP 代码真正执行的地方。

PHP-FPM 对此的处理机制是新的 worker 用修改后的 php.ini 配置,已经存在的 worker 执行完成后 kill 了,通过这种机制来平滑重启。
FastCGI

Php-fpm 既然是管理 FastCGI 进程的,那我们也需要了解一下 FastCGI 是什么。

早期的webserver只处理html等静态文件,处理不了像php等动态语言。为了解决不同的语言解释器(如php、python解释器)与webserver的通信,于是出现了 cgi协议。只要你按照cgi协议去编写程序,就能实现语言解释器与webwerver的通信。如 php-cgi 程序。

但是,webserver每收到一个请求,都会去fork一个cgi进程,请求结束再kill掉这个进程。这样有10000个请求,就需要fork、kill php-cgi进程10000次。这样很浪费资源,于是出现了cgi的改良版本:fast-cgi

fast-cgi 每次处理完请求后,不会kill掉这个进程,而是保留这个进程,使这个进程可以一次处理多个请求。这样每次就不用重新fork一个进程了,大大提高了效率。

PHP-CGI

PHP-CGI是一个实现了CGI协议的程序,用来解释PHP脚本的程序,通过调用 PHP 的 php_execute_script 函数来解析和运行 php 脚本。

PHP-CGI的不足:
	php-cgi变更php.ini配置后需重启php-cgi才能让新的php-ini生效,不可以平滑重启。
	直接杀死php-cgi进程,php就不能运行了。(PHP-FPM和Spawn-FCGI就没有这个问题,守护进程会平滑从新生成新的子进程。)

因为 PHP-CGI 只是个 CGI 程序,它自己本身只能解析请求,返回结果,不会进程管理,所以就出现了一些能够调度 PHP-CGI 进程的程序。

注意: PHP5.4以前 Php-fpm是用于管理 PHP-CGI 的,但 PHP5.4 以后,Php 内核集成了PHP-FPM,本身已经自带了解析PHP的功能,不只是做进程管理,也不再依赖 PHP-CGI 来解析PHP了。

Nginx 与 PHP 交互

当nginx接收到一个http请求时,通过 *.conf 配置文件找到对应的 server 。然后匹配 server 中的所有location,找到最匹配的。

而在 location 中的命令会启动不同的模块去完成工作,比如 rewrite 模块、index 模块。因此在 nginx模块 可以看作真正的劳动工作者。nginx 的模块 是被编译到 nginx 中的,属于静态方式。启动 nginx时,模块被自动加载。不像 apache,把模块单独编译成 so文件,在配置文件中指定是否加载。所以,单比模块加载方面,nginx 也比 apache 速度上有提升。

那nginx是怎么调用php的呢?先看下面的nginx中关于php的配置:


server {
	listen       80;
    server_name  www.test.com;
     
	#access_log  logs / host.access.log  main;

	location / {
        root   / newfolder / dist;
        index  index.html index.htm;
    }
      
	location ~ \.php$ {
		root           /home/www;
		fastcgi_pass   127.0.0.1:9000; # 指定将http代理到哪个fastcgi服务端接口
		....
	}
}

这个 location 指令把以以php为文件后缀的请求,交给 127.0.0.1:9000处理。 而这里的IP地址和端口(127.0.0.1:9000)就是 fastcgi 进程监听的IP地址和端口。

那这个fastcgi的配置IP和端口从何而来呢?在php-fpm.conf中可以看到如下:

pid = run/php-fpm.pid #pid设置,默认在安装目录中的var/run/php-fpm.pid,建议开启
error_log = log/php-fpm.log #错误日志,默认在安装目录中的var/log/php-fpm.log 如果设置为syslog,log就会发送给syslogd服务而不会写进文件里。
syslog.facility = daemon # 把日志写进系统log,linux还不够熟悉,暂时不用理会。
syslog.ident = php-fpm #系统日志标示,如果跑了多个fpm进程,需要用这个来区分日志是谁的。
log_level = notice #错误级别. 可用级别为: alert(必须立即处理), error(错误情况), warning(警告情况), notice(一般重要信息), debug(调试信息). 默认: notice.
emergency_restart_threshold = 60
emergency_restart_interval = 60s #表示在emergency_restart_interval所设值内出现SIGSEGV或者SIGBUS错误的php-cgi进程数如果超过 emergency_restart_threshold个,php-fpm就会优雅重启。这两个选项一般保持默认值。
process_control_timeout = 0 #设置子进程接受主进程复用信号的超时时间. 可用单位: s(秒), m(分), h(小时), 或者 d(天) 默认单位: s(秒). 默认值: 0.
daemonize = yes #后台执行fpm,默认值为yes,如果为了调试可以改为no。在FPM中,可以使用不同的设置来运行多个进程池。 这些设置可以针对每个进程池单独设置。

listen = 127.0.0.1:9000 #fpm监听端口,即nginx中php处理的地址,一般默认值即可。可用格式为: 'ip:port', 'port', '/path/to/unix/socket'. 每个进程池都需要设置.
listen.backlog = -1 #未accept处理的socket队列大小,-1 on FreeBSD and OpenBSD,其他平台默认65535,高并发时重要,合理设置会及时处理排队的请求;太大会积压太多,处理完后nginx在前面都等超时断开这个和fpm的socket连接了,就杯具了。不要用-1,建议1024以上,最好是2的幂值。
# 一个池共用一个backlog队列,所有的池进程都去这个队列里accept连接。
# 最大数量受限于系统配置 cat /proc/sys/net/core/somaxconn,系统配置修改:vim /etc/sysctl.conf,增加 net.core.somaxconn = 2000 则最大为2000,然后php最大的backlog可以到2000。
listen.allowed_clients = 127.0.0.1 #允许访问FastCGI进程的IP,设置any为不限制IP,如果要设置其他主机的nginx也能访问这台FPM进程,listen处要设置成本地可被访问的IP。默认值是any。每个地址是用逗号分隔. 如果没有设置或者为空,则允许任何服务器请求连接
listen.owner = www
listen.group = www
listen.mode = 0666 #unix socket设置选项,如果使用tcp方式访问,这里注释即可。
user = www #启动进程的帐户
group = www #启动进程的组

pm = dynamic #如何控制子进程,选项有static和dynamic。如果选择static,则由pm.max_children指定固定的子进程数。如果选择dynamic,则由下开参数决定:
pm.max_children = 20 #,子进程最大数
pm.start_servers = 10 #,启动时的进程数
pm.min_spare_servers = 10 #,保证空闲进程数最小值,如果空闲进程小于此值,则创建新的子进程
pm.max_spare_servers = 20 #,保证空闲进程数最大值,如果空闲进程大于此值,此进行清理
pm.max_requests = 1000 #设置每个子进程重生之前服务的请求数. 对于可能存在内存泄漏的第三方模块来说是非常有用的. 如果设置为 '0' 则一直接受请求. 等同于 PHP_FCGI_MAX_REQUESTS 环境变量. 默认值: 0.

通过配置中的 listen 参数可以监听本机的 9000 端口。

简单总结一下Nginx+PHP的运行原理:

1、nginx 和 php-fpm 启动,加载各自的配置文件。

  • nginx会根据 nginx.conf中的配置去监听指定的域名或IP启用其对应的配置。
  • php-fpm 初始化时会启动 master 和启动 start_servers 个 worker。php-fpm 的 master 主要是管理 worker、监听9000端口、worker 等待请求。
     

2、nginx 的 worker 进程直接管理每一个请求到 nginx的网络请求。当客户端请求到达 nginx 时,nginx通过 location 指令,将所有以php为后缀的文件都交给 127.0.0.1:9000 来处理。
 

3、nginx间接调用php:通过上面的配置我们知道 php-fpm 会默认监听本机的9000端口,nginx的worker进程会将请求移交给 php-fpm 的 worker 进程进行处理。并将 fastcgi 的环境变量和标准输入发送到 worker 进程。worker 进程完成处理后将标准输出和错误信息返回。当 worker 进程关闭连接时,请求便告处理完成,等待下次处理。

注意: fastcgi 的环境变量和标准输入可参考另一篇博客Nginx 之 fastcgi 常用参数详解
上面服务器处理完请求之后响应结果,结束后断开连接。

7、服务器响应HTTP请求

HTTP的响应报文也由三部分组成(响应行+响应头+响应体)。

响应行

响应行信息由三部分组成:HTTP协议版本 响应状态码,服务器会返回状态码来告诉浏览器本次请求的处理结果。

HTTP/1.1 200 OK

HTTP的响应状态码:

1xx 信息

  • 100 Continue - 服务器仅接收到部分请求,但是一旦服务器并没有拒绝该请求,客户端应该继续发送其余的请求。
  • 101 Switching Protocols 服务器转换协议:服务器将遵从客户的请求转换到另外一种协议。
     

2xx 成功

  • 200 OK 请求成功(其后是对GET和POST请求的应答文档。)
  • 201 Created 请求被创建完成,同时新的资源被创建。
  • 202 Accepted 供处理的请求已被接受,但是处理未完成。
  • 203 Non-authoritative Information 文档已经正常地返回,但一些应答头可能不正确,因为使用的是文档的拷贝。
  • 204 No Content 没有新文档。浏览器应该继续显示原来的文档。如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的。
  • 205 Reset Content 没有新文档。但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容。
    206 Partial Content 客户发送了一个带有Range头的GET请求,服务器完成了它。
     

3xx 重定向

  • 300 Multiple Choices 多重选择。链接列表。用户可以选择某链接到达目的地。最多允许五个地址。
  • 301 Moved Permanently 所请求的页面已经永久转移至新的url。
  • 302 Found 所请求的页面已经临时转移至新的url。
  • 303 See Other 所请求的页面可在别的url下被找到。
  • 304 Not Modified 未按预期修改文档。客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
  • 305 Use Proxy 客户请求的文档应该通过Location头所指明的代理服务器提取。
  • 306 Unused 此代码被用于前一版本。目前已不再使用,但是代码依然被保留。
  • 307 Temporary Redirect 被请求的页面已经临时移至新的url。

 
4xx 客户端错误

  • 402 Payment Required 此代码尚无法使用。
  • 403 Forbidden 对被请求页面的访问被禁止。
  • 405 Method Not Allowed 请求中指定的方法不被允许。
  • 406 Not Acceptable 服务器生成的响应无法被客户端所接受。
  • 407 Proxy Authentication Required 用户必须首先使用代理服务器进行验证,这样请求才会被处理。
  • 408 Request Timeout 请求超出了服务器的等待时间。
  • 409 Conflict 由于冲突,请求无法被完成。
  • 410 Gone 被请求的页面不可用。
  • 411 Length Required “Content-Length” 未被定义。如果无此内容,服务器不会接受请求。
  • 412 Precondition Failed 请求中的前提条件被服务器评估为失败。
  • 413 Request Entity Too Large 由于所请求的实体的太大,服务器不会接受请求。
  • 414 Request-url Too Long 由于url太长,服务器不会接受请求。当post请求被转换为带有很长的查询的get请求时,就会发生这种情况。
  • 415 Unsupported Media Type 由于媒介类型不被支持,服务器不会接受请求。
  • 416 Requested Range Not Satisfiable 服务器不能满足客户在请求中指定的Range头。
  • 417 Expectation Failed 执行失败。

 

5xx 服务端错误

  • 500 Internal Server Error 请求未完成。服务器遇到不可预知的情况。
  • 501 Not Implemented 请求未完成。服务器不支持所请求的功能。
  • 502 Bad Gateway 请求未完成。服务器从上游服务器收到一个无效的响应。
  • 503 Service Unavailable 请求未完成。服务器临时过载或当机。
  • 504 Gateway Timeout 网关超时。
  • 505 HTTP Version Not Supported 服务器不支持请求中指明的HTTP协议版本。
响应头

以下是部分响应头参数:

参数描述
Server客户端策略
Content-Type响正文的类型
Content-Length响应正文长度
Content-Charset响应正文使用的编码
Content-Encoding响应正文的压缩数据类型
Content-Language响应正文的使用的语言
Date响应时间
响应体

响应体中就包含了本次请求所需要的资源文件,浏览器会先从网络进程去下载这个资源,然后将资源提交给渲染进程进行渲染。

百度响应示例

我这里打开百度首页找的请求示例:

General:包含请求行和响应行
Request Headers:请求头
Response Headers:响应头
Payload:请求体
Response:响应体

8、客户端解析返回数据

客户端收到了服务器的响应数据包后,也开始逆向解析,一直解析到 HTTP 响应报文后,然后交给浏览器去渲染页面,网页就这样显示出来了。最后,浏览器关闭了,向服务器发起了 TCP 四次挥手,至此双方的连接就断开了。

9、与服务器断开连接

TCP的四次挥手


第一次挥手:Client 发送一个 FIN 包到服务器,并进入到 FIN_WAIT_1 状态,等待服务器确认。
第二次挥手:Server 接收到 SYN 并返回一个 SYN+ACK 包,此时服务器进入 CLOSE_WAIT 状态。
第三次挥手:Client 收到 Server 立刻进入 FIN_WAIT_2 状态(如果这个时候 Server 直接断开,则A将永远在这个状态。TCP协议里面并没有对这个状态的处理,但是Linux有,可以调整tcp_fin_timeout这个参数)。
第四次挥手:Server 向 Client 发送断开连接的信息,Client 收到信息之后,从 FIN_WAIT_2 状态结束。如果此时 Client 断开连接,Server 没有收到 Client 的ACK请求,Server 则会重新发送断开连接的请求,那么 Server 就再也收不到A的ACK请求了,所以TCP协议要求 Client 最后等待一段时间 TIME_WAIT。

TCP其他机制可自行查找这里就不多说了(报文校验、ACK应答、超时重传(发送方)、失序数据重传(接收方)、丢弃重复数据、流量控制(滑动窗口)和拥塞控制等机制)。

可参考网络篇:朋友面试之TCP/IP,回去等通知吧

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值