PHP面试题汇总 (二)
文章内容主要参考:https://github.com/ycrao/mynotes
问题来源:
http://tieba.baidu.com/p/3612369052
http://blog.csdn.net/hyr352114576/article/details/49638345
https://my.oschina.net/u/574366/blog/64814
for与foreach哪个更快?
参考答案:http://www.cnblogs.com/niniwzw/archive/2008/06/03/1212535.html
foreach
的效率要比 for
高很多,也许有很大的一个原因是 for
要进行很多次条件判断。所以以后能用 foreach
的地方就用 foreach
,可以提高1倍的效率。
如果循环内要调用函数,用 array_walk
最好,它的效率要比 for
高出1倍,要比 foreach
高出43%的效率。
PECL 和 PEAR 有什么区别?
参考答案:http://jingyan.baidu.com/article/e9fb46e1a3eb277521f76619.html
PECL (PHP Extension Community Library)
可以看作 PEAR (PHP Extension and Application Repository)
的一个组成部分,提供了与 PEAR
类似的功能。不同的是 PEAR
的所有扩展都是用纯粹的 PHP
代码编写的,用户在下载到 PEAR
扩展以后可以直接使用将扩展的代码包含到自己的 PHP
文件中使用。而 PECL
是使用 C
语言开发的,通常用于补充一些用 PHP
难以完成的底层功能,往往需要重新编译或者在配置文件中设置后才能在用户自己的代码中使用。
最直接的表述:PEAR
是 PHP
的上层扩展,PECL
是 PHP
的底层扩展。它们都是为特定的应用提供现成的函数或者类。
如何处理多服务器共享 Session ?
参考答案:http://www.toutiao.com/a6294758409293086977/
思路:引入统一session接入点
大致上有三种方式可以处理:
- 数据库/文件同步
session
cookie
同步session
- 缓存 (如
memcache
)同步session
- 延伸:单独提供session服务,所有机器从这个服务获取session数据
推荐使用cache和数据库结合的实现方式,保证高效和稳定
什么是跨站脚本?SQL注入?
1. 跨站脚本攻击的原理和防范
- 原理
XSS又叫CSS (Cross Site Script) ,跨站脚本攻击。它指的是恶意攻击者往Web页面里插入恶意脚本代码,而程序对于用户输入内容未过滤,当用户浏览该页之时,嵌入其中Web里面的脚本代码会被执行,从而达到恶意攻击用户的特殊目的。
跨站脚本攻击的危害:窃取cookie、放蠕虫、网站钓鱼 …
跨站脚本攻击的分类主要有:存储型XSS、反射型XSS、DOM型XSS
- 防范:过滤非法字符(html,js,css)(strip_tags、htmlspecialchars)
2. SQL注入
原理
SQL
注入是发生于应用程序数据库层的安全漏洞。用户输入的参数拼凑SQL查询语句,使用户可以控制SQL查询语句防范:不要使用拼接的sql,使用占位符
- 使用预编译语句
- 检查数据类型
参考:
http://blog.csdn.net/jbb0403/article/details/36626515
http://www.cnblogs.com/ITtangtang/p/3982297.html
3. 跨站请求伪造(CSRF)攻击
CSRF是Cross Site Request Forgery的缩写,乍一看和XSS差不多的样子,但是其原理正好相反,XSS是利用合法用户获取其信息,而CSRF是伪造成合法用户发起请求
- 原理
从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成以下两个步骤:1. 登录受信任网站A,并在本地生成Cookie。2. 在不登出A的情况下,访问危险网站B。
- CSRF如何防御
- 验证HTTP Referer字段
- 添加token验证
- 验证码
- 尽量使用POST,限制GET
- 在HTTP头部添加自定义属性
参考:
描述一下大流量高并发量网站的解决方案
- 确认服务器硬件是否足够支持当前的流量。
- 使用
memcache
或redis
缓存技术,将动态数据缓存到文件中,动态网页直接调用这些文件,而不必在访问数据库。 - 禁止外部的盗链。外部网站的图片或者文件盗链往往会带来大量的负载压力,因此应该严格限制外部对自身图片或者文件盗链,可以通过apache的URL重定向来防止盗链。
- 控制大文件的下载。大文件的下载会占用很大的流量,对于非SCSI硬盘来说会消耗,使得网站响应能力下降。
- 使用不同的主机分流主要流量。
- 使用流量统计软件。在网站上安装一个流量统计软件,可以即时知道哪些地方耗费了大量流量,哪些页面需要再进行优化。
如何防盗链
参考引用: https://yq.aliyun.com/articles/57931
- 设置Referer(原理:判断来源地址是否与白名单地址匹配)
- 签名URL(原理:签名URL,增加url的有效时间)
PHP内存管理机制与垃圾回收机制
参考答案:http://www.cnblogs.com/zk0533/p/5667122.html
php
的内存管理机制是:预先给出一块空间,用来存储变量,当空间不够时,再申请一块新的空间。
- 存储变量名,存在符号表。
- 变量值存储在内存空间。
- 在删除变量的时候,会将变量值存储的空间释放,而变量名所在的符号表不会减小。
php
垃圾回收机制是:
在5.2版本或之前版本,PHP会根据 引用计数 (
refcount
)值来判断是不是垃圾,如果refcount值为0,PHP会当做垃圾释放掉,这种回收机制有缺陷,对于环状引用的变量无法回收。在5.3之后版本改进了垃圾回收机制。具体如下:
如果发现一个 zval
容器中的 refcount
在增加,说明不是垃圾;
如果发现一个 zval
容器中的 refcount
在减少,如果减到了0,直接当做垃圾回收;
如果发现一个 zval
容器中的 refcount
在减少,并没有减到0,PHP
会把该值放到缓冲区,当做有可能是垃圾的怀疑对象;
当缓冲区达到了临界值,PHP
会自动调用一个方法去遍历每一个值,如果发现是垃圾就清理。
理解php内核中SAPI的作用
SAPI: Server Application Programming Interface 服务器端应用编程端口。先看一张php模块图
各种应用都是通过对应的SAPI与php进行交互的,SAPI相当于一个接口,使得php的核心实现不用关心各个应用交互的细节。虽然通过Web服务器和命令行程序执行脚本看起来很不一样,实际上它们的工作流程是一样的
通过cgi和cli模式下read_cookies的不同实现,可以看出sapi确实对下层php屏蔽了交互细节,当下层php核心要读取用户cookies时,只需要通过sapi_module_struct->read_cookies,而不需要关注上层应用的交互细节
参考:
为什么要对数据库进行主从分离?
参考答案:https://my.oschina.net/candiesyangyang/blog/203425
- 读写服务器可单独优化,提高可用性
- 降低写服务器压力
- 主从只负责各自的写和读,极大程度的缓解X锁和S锁争用,提高并发性
延伸:什么是X锁和S锁?
- 共享锁(S锁Share Locks)又称为读锁,若事务T对数据对象A加上S锁,则事务T只能读A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
- 排它锁:排它锁又称为写锁((eXclusive lock,简记为X锁)),若事物T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。
多线程和多进程的区别为?
参考答案:
http://www.cnblogs.com/kaituorensheng/p/3603057.html
- 进程是资源分配的最小单位,线程是CPU调度的最小单位
- 进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。
- 相同点:进程线程间切换都会消耗系统资源
ps:
进程可靠性高原因:比如fork多个子进程,单独某个进程出现问题,不影响其他进程,而多个子线程如果某个子线程挂了可能导致整个进程挂掉
TCP/IP 网络协议,OSI 7 层指是什么?
参考答案:http://blog.csdn.net/jenminzhang/article/details/47017741
TCP/IP
5层 指的是:
1 | 应用层 |
OSI
7层指的是:
1 | 应用层 文件传输,电子邮件,文件服务,虚拟终端 TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet |
TCP/IP知识点
参考:https://gaoshangs.github.io/2018-01-31-tcp_ip_point.html
三次握手
第一次握手:客户端发送syn包(syn=x)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RCVD状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
四次挥手
与建立连接的“三次握手”类似,断开一个TCP连接则需要“四次握手”。
第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不 会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,这时候主动关闭方会进入TIME_WAIT状态,等2MSL后即可回到CLOSED可用状态了。至此,完成四次挥手。
常见问题
1. TCP的三次握手过程?为什么会采用三次握手,若采用二次握手可以吗?
答:建立连接的过程是利用客户服务器模式,假设主机A为客户端,主机B为服务器端。
- TCP的三次握手过程:主机A向B发送连接请求;主机B对收到的主机A的报文段进行确认;主机A再次对主机B的确认进行确认。
- 采用三次握手是为了防止失效的连接请求报文段突然又传送到主机B,因而产生错误,造成资源浪费。
2. 为什么建立连接协议是三次握手,而关闭连接却是四次挥手呢?
建立连接时候ACK和SYN可以放到一个报文发送,而关闭连接时被动方可能未全部完成数据的发送,所以ACK和SYN回分两次发送。
3. 为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
CGI/FastCGI/php-fpm和nginx通信原理
cgi、fast-cgi协议
- cgi的历史
早期的webserver只处理html等静态文件,但是随着技术的发展,出现了像PHP等动态语言。
webserver处理不了了,怎么办呢?那就交给php解释器来处理吧!
交给php解释器处理很好,但是,php解释器如何与webserver进行通信呢?
为了解决不同的语言解释器(如php、Python解释器)与webserver的通信,于是出现了cgi协议。只要你按照cgi协议去编写程序,就能实现语言解释器与webwerver的通信。如php-cgi程序。
fast-cgi的改进
有了cgi协议,解决了php解释器与webserver通信的问题,webserver终于可以处理动态语言了。但是,webserver每收到一个请求,都会去fork一个cgi进程,请求结束再kill掉这个进程。这样有10000个请求,就需要fork、kill php-cgi进程10000次。
有没有发现很浪费资源?
于是,出现了cgi的改良版本,fast-cgi。fast-cgi每次处理完请求后,不会kill掉这个进程,而是保留这个进程,使这个进程可以一次处理多个请求。这样每次就不用重新fork一个进程了,大大提高了效率。
- php-fpm是什么
php-fpm即php-Fastcgi Process Manager.
php-fpm是 FastCGI 的实现,并提供了进程管理的功能。
进程包含 master 进程和 worker 进程两种进程。
master 进程只有一个,负责监听端口,接收来自 Web Server 的请求,而 worker 进程则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个 PHP 解释器,是 PHP 代码真正执行的地方。
- nginx和php-fpm运行原理和流程
Nginx不只有处理http请求的功能,还能做反向代理。Nginx通过反向代理功能将动态请求转向后端Php-fpm
www.example.com
Nginx| |
路由到www.example.com/index.php| |
加载nginx的fast-cgi模块| |
fast-cgi监听127.0.0.1:9000地址| |
www.example.com/index.php请求到达127.0.0.1:9000| |
php-fpm 监听127.0.0.1:9000| |
php-fpm 接收到请求,启用worker进程处理请求| |
php-fpm 处理完请求,返回给nginx| |
nginx将结果通过http返回给浏览器| |
参考
PHP路由技术的原理与实践
路由实现原理
用户通过指定的URL范式对后台进行访问,URL路由处理类进行处理后,转发给逻辑处理类,逻辑处理类将请求结果返回给用户。
URL范式和规则
一般为对搜索引擎友好,对用户友好的URL规则,比较流行的有两种:普通模式和
pathinfo
模式
- 普通模式
在 ThinkPHP 框架中,默认的URL格式即为普通模式,普通模式URL如下:index.php?m=home&c=user&a=login&v=value
其中 m 参数的值为模块名称, c 参数的值为控制器名称, a 参数的值为方法名称,之后的参数则为该方法中所要接收的其他 GET 请求参数- pathinfo模式
在 CodeIgniter 框架中,默认的URL格式为 pathinfo 模式,如下:index.php/controller/method/prarme1/value1
在 method 以后,就是方法接收的 GET 参数了,格式就是 名称/值
URL路由处理类
以index.php?c=user&a=login&v=value
普通模式为例,URL路由处理类主要完成对控制器类和方法的加载和判断是否存在,有则执行,没有则报错。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
include 'index.class.php';
include 'user.class.php';
// 对用户请求URL进行处理
$query = $_GET;
$controller = isset($query['c']) ? $query['c'] : 'indexController';
$action = isset($query['a']) ? $query['a'] : 'index';
if (class_exists($controller)) {
if (method_exists($controller, $action)) {
unset($_GET['c']);
unset($_GET['a']);
// 实例化用户请求类并调用方法
(new $controller())->$action();
} else {
echo '控制器' . $controller . '中不存在方法' . $action;
}
} else {
echo '不存在控制器' . $controller;
}
其中 unset() 掉两个get参数,只是为了对真正调用的方法造成其他影响。
逻辑处理类
逻辑处理类就是最终的业务逻辑,也就是真正的回应用户请求的代码片段。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/* index.class.php 文件源码 */
class indexController {
public function index(){
var_dump($_GET);
}
}
/* user.class.php 文件源码 */
class user {
public function index() {
echo '这里是User控制器';
}
public function login() {
var_dump($_GET);
}
}
nginx配置location总结
- 语法规则
- 已=开头表示精确匹配
如 A 中只匹配根目录结尾的请求,后面不能带任何字符串。 - ^~ 开头表示uri以某个常规字符串开头,不是正则匹配
- ~ 开头表示区分大小写的正则匹配;
- ~* 开头表示不区分大小写的正则匹配
- / 通用匹配, 如果没有其它匹配,任何请求都会匹配到
- 匹配顺序
- 首先匹配 =
- 其次匹配 ^~
- 其次是按文件中顺序的正则匹配 ~
- 最后是交给 / 通用匹配
- 当有匹配成功时候,停止匹配,按当前匹配规则处理请求
nginx支持PHP的PATHINFO模式配置
要求
解析http://www.test.com/admin/index.php/myModule/myController/myAction?key1=val1&key2=val2
,使通过$_SERVER可以获取如下键值:1
2
3
4
5
6
7
8
9
10$_GET = array (
'key1' => 'val1',
'key2' => 'val2',
)
$_SERVER = array(
'PATH_INFO' => '/myModule/myController/myAction',
'SCRIPT_FILENAME' => '/Data/code/workt/admin/index.php',
'SCRIPT_NAME' => '/admin/index.php',
...
)实现:使用nginx的fastcgi_split_path_info指令,具体配置如下
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
26
27
28##匹配nginx需要交给php-fpm执行的URI,先要允许pathinfo格式的URL能够被匹配到
##所以要去掉$
##nginx文档中的匹配规则为:^(.+\.php)(.*)$
##还有~ \.php这种写法 和 ~ \.php($|/)这种写法
##都是差不多意思没啥严格区别
##唯一区别就是有多个匹配php的location的话需要留意权重差异
location ~ ^(.+\.php)(.*)$ {
root /var/www/www.jjonline.cn/wwwRoot;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
##增加 fastcgi_split_path_info指令,将URI匹配成PHP脚本的URI和pathinfo两个变量
##即$fastcgi_script_name 和$fastcgi_path_info
fastcgi_split_path_info ^(.+\.php)(.*)$;
##PHP中要能读取到pathinfo这个变量
##就要通过fastcgi_param指令将fastcgi_split_path_info指令匹配到的pathinfo部分赋值给PATH_INFO
##这样PHP中$_SERVER['PATH_INFO']才会存在值
fastcgi_param PATH_INFO $fastcgi_path_info;
##在将这个请求的URI匹配完毕后,检查这个绝对地址的PHP脚本文件是否存在
##如果这个PHP脚本文件不存在就不用交给php-fpm来执行了
##否者页面将出现由php-fpm返回的:`File not found.`的提示
if (!-e $document_root$fastcgi_script_name) {
##此处直接返回404错误
##你也可以rewrite 到新地址去,然后break;
return 404;
}
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
参考: