PHP高并发编程

一级目录

二级目录

三级目录

php高并发编程swoole.workman.20210202

一 课程说明

内容:

多进程程序
多线程程序
协程程序
提高Web并发能力
高级IO及相关开源产品
长连接项目应用

收效:

了解多进程,多线程,协程的概念
使用php进行系统编程
可以使用php的socket开源框架
利用多进程,多线程,协程写出高效的程序
对php编程有一个全新的认识(不止专注与web开发)

多进程

二 多进程概念和准备

进程:

​ 是某段程序运行的实例

多进程:

​ 同一同一时间操作系统中有两个及以上的进程实例在执行

php实现多进程:

PHP 提供了进程控制的扩展 PCNTL。
PCNTL 实现了【Unix】 方式的进程创建,程序执行,信号处理以及进程终端。
结合 POSIX 系列函数获得更多系统底层的能力

场景:

群发任务:邮件、短信
多人任务:采集程序
并发服务:多进程服务器

准备工作:

  1. 安装 PCNTL,shmop扩展, 另外 POSIX是php内置扩展不用安装了

    PHP编译选项中开启 --enable-pcntl --enable-shmop 
    或者  用phpize编译安装
    
  2. 验证

    php -m   | grep -E "pcntl|shmop"
    

注意事项:

​ 进程控制不能在web服务器环境使用. php-fpm不支持

三 创建新进程

相关函数:

https://www.php.net/manual/en/ref.pcntl.php

https://www.php.net/manual/en/ref.posix.php

pcntl_fork	创建新进程
pcntl_waitpid  等待或返回fork的子进程状态
posix_getpid   返回当前进程 id
posix_getppid 取得父进程 

psg命令看进程数

补充(重要):

子进程和父进程共享程序的正文段
子进程拥有父进程的数据空间和堆占的副本(COW)
父子进程将继续执行fork之后的程序代码
fork之后,父进程和子进程执行顺序不确定,取决于系统调度

四 php代码执行外部程序

https://www.php.net/manual/en/function.escapeshellcmd.php

相关函数:

exec 	执行一个外部程序

类似的相关函数
shell_exec, system, passthru 
参数、返回值用法、表现形式不一样
system是直接打印出结果

escapeshellcmd() 
/*escapes any characters in a string that might be used to trick a shell command into executing arbitrary commands. This function should be used to make sure that any data coming from user input is escaped before this data is passed to the exec() or system() functions, or to the backtick operator. */
pcntl_exec 	在当前进程空间执行指定程序

pcntl_exec 并不创建进程,进程 id 不变,进程执行的程序完全替换为新程序。
pcntl_exec 只是用磁盘上的新程序替换了当前进程的正文段、数据段、堆段。
pcntl_exec 与 exec 的区别

1.都能执行外部程序
2.参数、返回值用法不一样
3.pcntl_exec 执行完外部程序之后进程直接退出,其后的逻辑不再执行
   exec 只是调用了外部程序,外部程序执行完毕之后继续执行当前逻辑
   
   
/*The pcntl_exec() function works exactly like the standard (unix-style) exec() function.  It differs from the regular PHP exec() function in that the process calling the pcntl_exec() is replaced with the process that gets called.  This is the ideal method for creating children.  In a simple example (that does no error checking):*/

switch (pcntl_fork()) {
  case 0:
    $cmd = "/path/to/command";
    $args = array("arg1", "arg2");
    pcntl_exec($cmd, $args);
    // the child will only reach this point on exec failure,
    // because execution shifts to the pcntl_exec()ed command
    exit(0);
  default:
    break;
}

// parent continues
echo "I am the parent";

--

/*since this is not being executed through a shell, you must provide the exact path from the filesystem root.  Look at the execve() man page for more information.*/   

五 监控子进程

https://blog.csdn.net/fanbird2008/article/details/6593084/

https://blog.csdn.net/weixin_33872660/article/details/86318516

监控目标: 将之前的创建子进程的代码,封装一个fork函数。当前子进程数小于启动时预设的进程数时,我们就 fork 一个子进程。

Using “kill -0 NNN” in a shell script is a good way to tell if PID “NNN” is alive or not:

相关函数:

pcntl_wait 		等待或返回fork的子进程状态(任意)
			    等同 pcntl_waitpid(-1, $status, 0)	都是等待任意子进程

pcntl_wtermsig	返回导致子进程中断的信号
展示所有信号
   kill –l
  
中断操作
   kill –INT / -QUIT / -KILL / -TERM pid

中断的区别
   1.都可以让进程退出。
   2.有的可以捕获有的不能捕获,类似 exception 与 exit 的区别。

六 守护进程化

https://www.php.net/manual/en/function.posix-setsid.php

https://www.php.net/manual/en/function.chdir.php

https://www.php.net/manual/en/function.umask.php

如何守护进程化
       1.创建子进程,终止父进程
       2.在子进程中创建新会话, 子进程提升成为会话组长
       3.改变工作目录(默认继承了父进程的当前工作目录)
       4.重设文件创建掩码(默认继承了父进程的文件创建掩码)
       5.关闭文件描述符(默认继承了父进程打开了的文件)

相关函数
       1.pcntl_fork, exit  创建子进程,退出进程
       2.posix_setsid     让当前进程变为主会话
       3.chdir('/')       当前工作目录改为 root 目录
       4.umask(0)         改变当前的 umask 为最宽松掩码
       5.fclose          关闭一个已打开的文件指针

七 任务控制信号

目标: 利用发送控制信号, 来实现上一集中的程序的 start stop reload操作

是一种通知异步事件的整数。 可以通过kill -l查看

处理方法
1.忽略此信号
	大多数信号都可以使用这种方式进行处理
	但有两种信号不能忽略,SIGKILL 和 SIGSTOP
	SIGKILL 和 SIGSTOP 向内核和超级用户提供使进程终止的可靠方法
2.捕捉信号
	通知内核在某种信号发生时,调用一个用户函数,执行希望对事件的处理
3.执行系统默认动作
	大多数信号的系统默认动作是终止该进程
常见信号
2) SIGINT		终端终端符 ctrl+c,默认动作-终止
3) SIGQUIT	终端退出符 ctrl+\ ,默认动作-终止
9) SIGKILL	终止信号,不能被捕获,默认终止
10) SIGUSR1	用户定义信号,默认动作-终止
12) SIGUSR2	用户定义信号,默认动作-终止
13) SIGPIPE	向断开的管道写内容,默认动作-终止
14) SIGALRM	定时器超时,默认动作-终止
15) SIGTERM	终止信号,kill 命令发送的系统默认信号,默认动作-终止
17) SIGCHLD	子进程状态改变,默认动作-忽略
19) SIGSTOP	终止信号,不能被捕获,默认终止
相关函数
pcntl_signal 		安装一个信号处理器
posix_kill 			给进程发送一个信号

其它
pcntl_alarm			为进程设置一个 alarm 闹钟信号
pcntl_signal_dispatch	调用等待信号的处理器
pcntl_async_signals	开启或关闭异步信号处理
pcntl_wtermsig 		返回导致子进程中断的信号

八 多进程间的通信 — 管道

https://www.php.net/manual/en/function.popen.php

https://www.php.net/manual/en/function.proc-open.php

https://blog.csdn.net/figli/article/details/41007573

解释:
管道是Unix系统进程通信的最古老方式,所有Unix系统都提供此种通信机制。

分类:
普通管道:无文件,只能在两个相关的两个进程之间使用,且有相同的祖先进程
有名管道(FIFO):系统中有可见的管道文件,不相关的进程也能交换数据

相关函数
popen		打开进程文件指针(单向管道)
pclose		关闭用 popen 打开的指向管道的文件指针

proc_open	执行一个命令,并且打开用来输入/输出的文件指针(双向管道)
proc_close	关闭由 proc_open 打开的进程并且返回退出码

posix_mkfifo	创建一个 fifo 文件(有名管道)

九 多进程间的通行 — 共享内存

共享内存
共享内存指在多处理器的计算机系统中,可以被不同CPU访问的大容量内存。
共享内存是Unix下的多进程之间的通信方法,程序间通过共享内存来传递信息。

特点
数据不需要在客户进程和服务器进程之间复制,所以是[最快的一种进程通信]。
函数
shmop_open		创建或打开共享内存块
shmop_size		取得共享内存块大小
shmop_write		写入数据到共享内存块
shmop_read		从共享内存块读取数据
shmop_close		关闭共享内存块
shmop_delete	删除共享内存块

共享内存块查看: ipcs -m

十 多进程间的通信 — 消息队列和信号量

消息队列
消息队列是消息的链接表,存储在内核中,由消息队列标识符标识。

信号量
与进程通信不同,它是一个计数器,用于为多个进程提供对共享数据对象的访问。
保证两个或多个关键代码不被并发调用。(类似于文件锁 )
安装
开启消息队列,带上编译选项: --enable-sysvmsg
开启信号量,  带上编译选项: --enable-sysvsem
开启共享内存,带上编译选项: --enable-sysvshm
    
与shmop区别
Semaphare(就是上面三个,php官方上的Semaphore Functions) 这个模块提供对 System V 操作系统函数的封装。  有了他就同时具有了 消息队列,信号量,共享内存三个功能

https://www.php.net/manual/en/ref.sem.php
消息队列函数
ftok					把路径和项目名转为一个给进程通信使用的 key
msg_queue_exists		检查一个消息队列是否存在
msg_get_queue		创建或获取一个消息队列
msg_stat_queue		返回消息队列数据结构的信息
msg_set_queue		设置消息队列数据结构的信息
msg_send			发送一条消息到消息队列
msg_receive			从消息队列中接收一条消息
msg_remove_queue	销毁一个消息队列
信号量函数
sem_get			得到一个信号量 id
sem_acquire		获取一个信号量
sem_release		释放一个信号量 
sem_remove		移除一个信号量
共享内存函数
shm_attach		创建或打开一个共享内存段
shm_detach		从一个共享内存段断开
shm_get_var		从共享内存中返回一个变量
shm_has_var		检查一个变量是否存在
shm_put_var		在共享内存中插入或更新一个变量
shm_remove_var	从共享内存中移除一个变量
shm_remove		从系统中移除共享内存

十一 网络进程通信

  1. 之前学习了,在本机中 可以通过 管道, fifo,共享内存,消息队列,信号量来进行通信。

  2. 网络进程通信, 不同计算机上的进程相互通信的机制。

    ---- 可以是socket, 也可以是UNIX Domain Socket(unix本地套接字)。

  3. 套接字描述符在unix系统中被当做是一个文件描述符

  4. 对比:
    Create - bind - [setopt] - listen - accept - [read/write] - close (Server)
    Create - [setopt] - connect - [read/write] - close (Client)

sockets 扩展安装
	开启PHP编译选项 --enable-sockets
服务端实现
1.创建套接字描述符 – create	*
2.套接字与地址关联 – bind	*
3.设置套接字选项 – setoption	
4.监听套接字 – listen		*
5.接收请求 – accept			*
6.数据传输 – read/write		*
7.关闭套接字 – close		*
客户端实现
1.创建套接字描述符 – create	*
2.设置套接字选项 – setoption	
3.建立连接 – connect		*
4.数据传输 – read/write		*
5.关闭套接字 – close		*

十二 网络进程通信 --封装

streams 扩展
对协议和网络通信的更高层次封装,操作更方便

十三 总结和优化

多进程实现
进程控制扩展:
	pcntl、posix、shmop、semaphore、sockets、streams
使用函数:
	pcntl_*、posix_*、shmop_*、msg_*、sem_*、socket_*、stream_*
两种用法
任务处理器:fork 多个子进程,执行完任务后退出
网络服务器:fork 多个子进程,子进程监听端口,独立解析协议处理请求
		     开源库有 workerman/workerman,react/react,phvia/firman 
进程通信: 

几种方式都可以运行在 php-fpm 模式;
有名管道会阻塞在一端,读先运行,则阻塞在读,等待写操作;写先运行,则阻塞在写,等待读操作。

进程通信几种方式: 
管道
	无名管道 popen、proc_open 	父子进程通信,不创建额外文件
	有名管道 posix_mkfifo		父子进程通信,创建 pipe 文件
共享内存
	shmop_*,			最快的 IPC 方式,可以在独立进程间通信
	ipcs/ipcrm -m 			查看/删除系统的共享内存块
消息队列与信号量
	msg_*、sem_*			可以在独立进程间通信
	ipcs/ipcrm -q/-s		查看/删除系统的消息队列/信号量
	共享内存和消息队列存在的形式相似,但有本质区别
	一个是共享数据,一个是队列,队列有其自身特性,消费完就删除内容
	共享内存块数据一直存在,直到清除或内存块被删除。
网络套接字
	socket_*、stream_*
守护进程的优化点:
出错日志:syslog
单实例守护进程:给文件上记录锁保证单实例
守护进程的惯例:锁文件通常是 /var/run 下的 name.pid,在配置文件 /etc 下;
			    启动或重载的时候才读取一次配置文件。

十四 多进程实战 – 采集图片

@doc https://github.com/FriendsOfPHP/Goutte

安装
$ composer require fabpot/goutte --prefer-dist

使用
use Goutte\Client;
$client = new Client();
$crawler = $client->request(‘GET’, $url);  
$crawler->filter(‘a’)->each(function($node) {
	$node->link();
}); 

多线程

十五 多线程程序准备

线程
线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行,也可以把它理解为代码运行的上下文,所以线程基本上是轻量级的进程。
多线程
多个线程在单进程环境中执行多个任务。
一个进程中的所有线程都可以访问该进程的组成部件,如文件描述符和内存。
注意
只要单个资源需要在多个用户间共享,就必须处理一致性问题。
通过提高资源使用率来提高系统效率,但是线程控制时需要防止死锁发生。总结多线程的定义:

多线程的定义:在一个进程内的可以独立运行的上下文。每个都共享进程内的文件描述符和内存。 需要做好线程控制,防止死锁的发生。

多线程程序,安装线程安全的php, 以及 pthreads扩展

https://www.cnblogs.com/kenshinobiy/p/4671275.html

https://www.cnblogs.com/fanweisheng/p/11333455.html

https://blog.csdn.net/zhou75771217/article/details/83303058

pthreads 扩展
PHP编译启用ZTS:--enable-maintainer-zts
安装 pecl 扩展 pthreads:http://pecl.php.net/package/pthreads

十六 创建新线程

多进程

1.使用多进程, 子进程结束以后, 内核会负责回收资源
2.使用多进程, 子进程异常退出不会导致整个进程Thread退出. 父进程还有机会重建流程.
3.一个常驻主进程, 只负责任务分发, 逻辑更清楚.
4.多进程方式更加稳定,另外利用进程间通信(IPC)也可以实现数据共享。
5.共享内存,这种方式和线程间读写变量是一样的,需要加锁,会有同步、死锁问题。
6.消息队列,可以采用多个子进程抢队列模式,性能很好

多线程

1.线程是在同一个进程内的,可以共享内存变量实现线程间通信
2.线程比进程更轻量级,开很大量进程会比线程消耗更多系统资源
3.多线程也存在一些问题:
4.线程读写变量存在同步问题,需要加锁
5.锁的粒度过大存在性能问题,可能会导致只有1个线程在运行,其他线程都在等待锁
6.同时使用多个锁,逻辑复杂,一旦某个锁没被正确释放,可能会发生线程死锁
7.某个线程发生致命错误会导致整个进程崩溃
模拟环境
$ alias pht="docker run -it --rm -v /home/workspace:/tmp phvia/php70-pthreads "
$ cd /home/workspace && pht /tmp/demo.php

参考
@使用的Docker镜像 https://hub.docker.com/r/phvia/php70-pthreads/ 
@关于Docker的安装 https://github.com/phvia/dkc
pthreads v3 基本类继承链:
		Threaded -->  Thread  --> Worker
解释:
       Threaded 提供 pthreads 基本功能,包括同步以及帮助接口
       Thread 提供 start 等方法。
       Worker 是一个具有持久化上下文的线程对象。
       Pool 是 Worker 线程对象池,是标准PHP对象,并没有继承 Threaded 类,不可以在多个线程上下文中共享同一个 Pool 对象。
       Volatile 用来表示可变的 Threaded 类中的 Threaded 属性(默认是不可变的),它也可以被用来在 Threaded 上下文中存储数组。

十七 线程同步–互斥锁

线程同步
同步就是协同步调,按预定的先后次序进行运行。
在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。

互斥锁
线程同步的一种方式,保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
同步控制方法
Threaded::synchronized()	在发起调用的线程上下文中获取对象同步锁, 然后同步执行代码块
Threaded::wait()			让发起调用的线程上下文进入等待, 直到收到其它线程的唤醒通知(notify)Threaded::notify()    	  给对象发送唤醒通知

十八 线程同步 – 读写锁

自旋锁
自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用,
它们在任何时刻,最多只能有一个保持者,也就是获得锁。

区别互斥锁
对于互斥锁,如果资源被占用,资源申请者只能进入睡眠状态;但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋”一词就是因此而得名。
读写锁
*	读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。
* 	相对于自旋锁而言,读写锁能提高并发性,写者是排他性的,一个读写锁同时只能有一个写者或多个读者。
*	如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁

十九 总结和优化

概念
* 线程,多线程 基本概念。
* PHP实现多线程的扩展,pthreads。
* phtreads v3 基本类。
* 线程间同步的方式(join,wait,notify),数据存储,静态成员。
* 很多PHP扩展或函数未对多线程场景进行设计。
* 代码线程安全:互斥锁(synchronization,wait,notify)、自旋锁、读写锁。
* PHP7.2以上版本才是真正的线程安全版本。
多线程特点
* 单核处理器也可以运行多线程程序。
* 多线程程序由操作系统在多个线程之间快速切换,看起来像在同时运行。
* 线程可以利用机器的多处理器和多核,但不一定更快,需要控制代码中锁的数量,否则可能会像单线程一样慢。
设计高效的多线程程序
减少线程需要访问的共享数据的数量,那么就不需要互相等待(避免锁),速度会大大提高。

二十 多线程查询数据库

多线程查询
$dsn = "sqlite:/tmp/data.db";
$db = new PDO($dsn);
$db->exec("create table users(id int, name varchar(255))");
$stat = $db->query(‘select * from users’);
$result = $stat->fetchAll(PDO::FETCH_ASSOC);

协程

二十一 协程

协程
协程与子例程(某个主程序的一部分代码)一样,协程也是一种程序组件。
协程(coroutine)不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。

对比
一个程序可以包含多个协程,对比一个进程可以包含多个线程,多个线程相对独立,有自己的上下文,切换受系统控制;协程也相对独立,有自己的上下文,但切换由自己控制,从当前协程切换到其它协程由当前协程来控制。
协程的支持
Lua5  --- 内置了对协程序的支持
Python的yield关键字
PHP5.5的yield关键字

二十二 生成器使用

啥事迭代器?

迭代器(Iterator)

可在内部迭代自己的外部迭代器或类的接口。
也就是在类中实现接口中的方法,按照不同顺序返回不同的值,这样就可以像遍历数组一样遍历该类的对象。
SPL 迭代器

PHP提供的用于日常任务的迭代器,以遍历不同的对象。

接口
Countable 接口,		实现 Countable 可被用于 count() 函数。
OuterIterator 接口,	实现 OuterIterator 可用于迭代外部迭代器。
RecursiveIterator 接口,实现 RecursiveIterator 可用于递归迭代外部迭代器。
IteratorAggregate 接口,聚合式迭代器,创建外部迭代器的接口
ArrayAccess 接口,		提供像访问数组一样访问对象的能力。
Serializable 接口,		自定义序列化的接口。
SplObserver/SplSubject 接口,用于实现观察者模式。
ArrayObject 类,		允许像数组一样使用的类。
生成器(Generator)
实现了对象迭代,比自己实现 Iterator 性能要高,复杂性低。

特点
生成器函数第一次调用时,返回内部生成器对象,它与单向迭代器对象一样实现了迭代器接口,并提供方法操作生成器状态、发送和返回数据。
允许在代码中迭代一组数据取代在内存中创建,减少内存占用。
与函数区别,普通函数返回一个值,而一个生成器可以 yield 生成许多它所需要的值。
生成器语法

yield 关键字,yield 会返回一个值给循环调用此生成器的代码,并且只是暂停执行生成器函数。
生成器用法

生成值					yield 123;
指定键名来生成值			yield 123 => 5;
生成 NULL 值				yield;
使用引用来生成值			通过在函数名前加一个引号
生成器委托 – yield from		yield from [3, 4];
						yield from seven();
						yield from new ArrayIterator([8, 9]);

二十三 多任务调度

PHP协程

协程的支持是在生成器的基础上,增加【回送数据给生成器】的功能,
这就把生成器到调用者的单向通信转变为两者之间的双向通信。

生成器类
Generator::current	返回当前产生的值
Generator::key		返回当前产生的键
Generator::next		生成器继续执行
Generator::rewind		重置迭代器,如果迭代已经开始了会抛出一个异常
Generator::send	        向生成器中传入值,并当做yield 的结果,继续执行生成器(重要)
Generator::valid		检查迭代器是否被关闭
Generator::__wakeup 	序列化回调
Generator::throw		向生成器中抛入一个异常

多任务调度

并发运行多个任务,使用调度器在不同任务之间进行切换,类似操作系统的调度。

二十四 课程总结

https://www.jianshu.com/p/5bbefcac9e0b

协程概念
对比进程,线程
协程的支持
迭代器(Iterator)
生成器(Generator)
生成器特点,语法,用法
PHP协程
生成器类
多任务调度
优缺点

轻量,节省系统资源,性能高。
借助 yield 实现协程不容易理解,管理难度高

协程框架
Swoole2.0
ZanPHP(不推荐)

提高Web并发能力

二十五 提高web并发能力 — 调优参数

前言:
nginx + php-fpm 环境下无法使用 pcntl、pthreads,那么提高并发就需要通过其它方式尽可能缩短程序的响应时间,增加任务进程数。

PHP配置(php.ini)
开启 opcache  (PHP5.5+ 已包含扩展,开启加选项 --enable-opcache)
opcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储字节码的好处就是省去了每次加载和解析 PHP 脚本的开销。

opcache 配置项:
opcache.enable=1				启用操作码缓存
opcache.revalidate_freq=60		检查脚本时间戳是否有更新的周期(默2秒)
opcache.enable_file_override=1	在调用 file_exists 时检查操作码缓存
opcache.file_cache=1			配置二级缓存目录并启用二级缓存
opcache.huge_code_pages=1	    启用将PHP代码拷贝到 HUGE PAGES 中

@doc http://php.net/manual/zh/opcache.configuration.php
PHP-FPM配置(www.conf)

默认是动态创建子进程:
pm=dynamic					动态创建子进程模式(默认)
pm.max_children=50			最多子进程(25M * 50 = 1.2G, 根据系统内存配置进程数)
pm.start_servers=10			初始启动进程数
pm.min_spare_servers=10		最小空闲进程数
pm.max_spare_servers=20		最大空闲进程数

在并发很大的时候,可以使用固定子进程数量:
pm=static					固定子进程模式(降低创建进程的开销)
pm.max_children=50			最多子进程(25M * 50 = 1.2G)

slowlog=log/$pool.log.slow		慢请求记录的日志文件
request_slowlog_timeout=5		慢请求时间(s 秒,m 分,h 小时,d 天)
request_terminate_timeout=30	当 php.ini 中 max_execution_time 没有终止脚本执行时,强制 kill 工作进程
rlimit_files=65535				可以打开最大的文件描述符数(ulimit -a)

二十六 提高web并发能力高效数据结构

数据结构

SPL 提供了一套标准的数据结构,按底层实现进行分组,定义了一般应用领域。
@doc http://php.net/manual/zh/book.spl.php
双向链表
双链表(DLL)是一个链接到两个方向的节点列表。当底层结构是 DLL 时,迭代器的操作、对两端的访问、节点的添加或删除都具有 O(1) 的开销。因此,它为栈和队列提供了一个合适的实现。

SplDoublyLinkedList

栈
SplStack 类通过使用一个双向链表来提供栈的主要功能。

队列
SplQueue 类通过使用一个双向链表来提供队列的主要功能。

堆
SplHeap 提供堆的主要功能

最大堆
SplMaxHeap 提供堆的主要功能,保持最大值在顶部。

最小堆
SplMinHeap 提供堆的主要功能,保持最小值在顶部。

顺序队列
SplPriorityQueue 提供顺序队列的主要功能,使用最大堆实现。

固定数组
SplFixedArray 提供数组的主要功能。
与普通 PHP 数组的区别是,SplFixedArray 固定长度并且只允许在范围内的整数索引。优点是比普通数组更快。

对象存储
SplObjectStorage 提供对象到数据的映射。这在需要解决唯一的对象标示的例子中有用。

二十七 并发访问

cURL

PHP支持 libcurl 库,只要编译完的PHP设置了支持 cURL 扩展,就可以使用 cURL 函数了。

函数
curl_init					初始化 cURL 会话
curl_setopt				设置 cURL 传输选项
curl_setopt_array			为 cURL 传输会话批量设置选项
curl_multi_init				返回一个新的 cURL 批处理句柄
curl_multi_add_handle		向 cURL 批处理会话中添加单独的 cURL 句柄
curl_multi_exec 			运行当前 cURL 句柄的子连接
curl_multi_remove_handle	移除 cURL 批处理句柄资源中的某个句柄
curl_multi_close			关闭一组 cURL 句柄	

二十八 后台快速执行

CGI、FastCGI、FPM 三者区别

CGI(Common Gateway Interface – 通用网关接口)是一种重要的互联网技术,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据,CGI 描述了服务器和请求处理程序之间传输数据的一种标准。
CGI 可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。

FastCGI(Fast Common Gateway Interface - 快速通用网关接口)像是一个常驻型的 CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去 fork 一次。它还支持分布式的计算,即 FastCGI 程序可以在网站服务器以外的主机上执行并且接受来自其他网站服务器的请求。
FastCGI 是语言无关的、可伸缩架构的 CGI 开放扩展,其主要行为是将 CGI 解释器进程保持在内存中并因此获得较高的性能。

FPM(php-fpm)
FPM(FastCGI Process Manager- FastCGI 进程管理器)是一个可选的 PHP FastCGI 实现并且附加了一些对高负载网站很有用的特性。
这个 SAPI 在 PHP5.3.3 以后已经被绑定。

FPM函数(只有一个函数):

fastcgi_finish_request	冲刷(flush)所有响应的数据给客户端并结束请求			
客户端结束连接后,需要大量时间运行的任务能够继续运行,提高了用户体验,但是不能提高程序总的并发量。

二十九 异步执行

www.cnblogs.com/farwish/p/9513100.html

异步执行

异步执行是指语句在异步执行模式下,各语句执行结束的顺序与语句执行开始的顺序并不一定相同。
异步执行方式使应用程序能摆脱单个任务的限制,提高了灵活性和应用程序的执行效率(实现多任务并行执行)。但异步执行模式增加了编程的复杂性,特别是编写互用性要求较高的程序。
PHP异步执行

1.popen(‘php test.php &’, ‘r’)	打开进程指针来执行命令

2.耗时任务放入队列中,异步消费执行。
    https://www.cnblogs.com/farwish/p/9513100.html

高级IO及相关开源产品

三十 非阻塞IO

阻塞I/O

可能会使进程永远阻塞的一类I/O操作。

如果某些文件类型(比如管道)的数据并不存在,读操作可能会使调用者永远阻塞。
如果数据不能被相同的文件类型立即接受,写操作可能会使调用者永远阻塞。
对已经加上强制性记录锁的文件进行读写。
某些进程间通信函数。
非阻塞I/O

非阻塞I/O使发出 open、read、write 这样的I/O操作不会永远阻塞。
如果这种操作不能完成,则调用立即出错返回,表示该操作如继续执行将阻塞。
指定非阻塞I/O的方法(pecl扩展)

如果调用 dio_open 获得描述符,则可指定 O_NONBLOCK 标志。
对于已经打开的一个描述符,则可调用 dio_fcntl,由该函数打开 O_NONBLOCK 文件状态标志。

三十一 IO多路复用

一个进程如何在多个文件描述符上读取数据?
使用非阻塞I/O可在多个文件描述符读取数据。将两个输入描述符都设置为非阻塞,对第一个描述符发一个 read,如果该输入上有数据,则读数据并处理它,如果无数据可读则该调用立即返回,然后对第二个描述符作同样的处理。在此之后等待一定的时间(若干秒),然后再尝试从第一个描述符读(轮询)。

借助非阻塞I/O来监听多个描述符的缺点
浪费CPU时间,大多数时间是无数据可读,执行 read 系统调用浪费了时间,循环后等待多长时间再执行下一轮循环也很难确定。所以应避免使用非阻塞IO轮询。

I/O多路复用
对当前进程的复用,来对多个描述符进行事件监听。

I/O多路复用执行过程
先构造一张感兴趣的描述符的列表,然后调用一个函数(I/O多路复用函数),直到这些描述符中的一个已准备好进行I/O时,该函数才返回。select,poll,epoll 使我们能够执行I/O多路复用。在从这些函数返回时,进程会被告知哪些描述符已准备好可以进行I/O。

I/O多路复用优点
由 select 来监听描述符,避免大量的空轮询。

I/O多路复用的方式对比
select – 同步的I/O多路复用
	     监控多个文件描述符(读、写、异常),等待一个或多个文件描述符变成就绪状态后进行I/O操作。select 只能监控文件描述符数量少于 FD_SETSIZE 的值。PHP中 Socket、Streams、Event 扩展支持。

poll – 在文件描述符上等待一些事件
	  和 select 执行的任务类似,监控的文件描述符集的存储方式不一样,没有 FD_SETSIZE 这个限制。PHP中 Event 扩展支持。   

epoll – I/O事件通知能力
	    和 poll 执行的任务类似,支持边缘触发和水平触发,对于大量监控的文件描述符可以很好扩展。使用水平触发时,语义和 poll 一样,边缘触发针对更高性能。PHP中 Event 扩展支持。

三十二 异步IO

异步IO

和同步IO相对,当一个异步过程调用发出后,调用者不能立刻得到结果。使用了异步IO,在发起IO请求到实际使用数据这段时间内,程序还可以继续做其他事情。

有哪些形式的异步

select 和 poll 可以实现异步形式的通知。
Posix 异步 IO 接口(PHP 的 Eio 扩展)。
Eio 注意

每一个请求都在一个线程中执行,所以顺序调用的执行顺序是不确定的,比如:先调用函数创建文件,接着就调用函数执行写入,这是不正确的。

三十三 异步非阻塞Socket-Workerman

@doc http://doc.workerman.net/

Workerman特性
纯PHP开发
支持PHP多进程  ---- pctnl
支持TCP、UDP
支持长连接
支持各种应用层协议   ---- txt等
支持 Libevent       --- select多路复用
支持平滑重启
环境要求

PHP>=5.4
pcntl, posix, sockets, streams 扩展
event 扩展可选   ---  可以使用epoll  和 poll
安装使用

composer require workerman/workerman
或者
clone 代码库,然后 include Autoloader.php

三十四 异步通信引擎-Swoole

Swoole特性

PHP标准扩展(与普通扩展不同的地方在于,接管了PHP请求的生命周期,作为 Server 运行)
支持PHP多进程、多线程、协程
支持TCP、UDP
支持长连接
支持各种应用层协议
支持底层操作接口(Memory、Process、Pool)
环境要求

类 Unix 平台
Linux 内核 2.3.32 以上
gcc4.4 以上
Swoole-1.x 要求 >= PHP-5.3.10
Swoole-2.x 要求 >= PHP-7.0.0
Swoole-4.x 要求 >= PHP-7.1.0
只需要基本PHP扩展,不依赖 pcntl、posix、sockets、streams、sysvmsg
编译安装

pecl install swoole(推荐)
或者
下载源码包后手动执行 ./configure && make && make install 编译安装

@doc https://wiki.swoole.com/wiki/page/6.html
@课程答疑 https://github.com/farwish/PCP/issues

====长连接项目

三十五 HTTP与WebSocket协议

HTTP

HTTP(HyperText Transfer Protocol,超文本传输协议)是用于从 WWW 服务器传输超文本到本地浏览器的应用层通信协议。
HTTP协议采用请求/响应模型。
HTTP协议格式 – 客户端请求

GET /index.html HTTP/1.1
Host: www.github.com
=====================

HTTP协议格式 – 服务器响应

HTTP/1.1 200 OK
Date: Mon, 23 Nov 2018 22:23:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 138

<html>
	<body>Hello</body>
</html>

PHP 实现 HTTP 服务的步骤

1.创建服务端套接字;然后接受请求
2.解释请求行确定请求文件
3.从文件系统中获取请求文件
4.创建被请求的文件组成的HTTP响应报文,发送给客户端
5.请求文件不存在返回错误码404

@ref https://www.cnblogs.com/farwish/p/8418969.html

WebSocket

WebSocket 协议是基于 TCP 的应用层网络协议,支持客户端与远程主机之间进行全双工通信。
在实现 WebSocket 连接过程中,需要通过浏览器发出 WebSocket 连接请求,服务器回应“握手”,然后浏览器和服务器之间就形成了一条快速通道。
WebSocket 协议握手 – 客户端请求

GET /chat HTTP/1.1
Host: www.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xxxxxxxxxxxx
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
WebSocket 协议握手 – 服务器响应(握手)

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: xxxxxxxxxxx
Sec-WebSocket-Protocol: chat

@doc https://en.wikipedia.org/wiki/WebSocket
PHP 实现 WebSocket 握手

$response_header = “HTTP/1.1 101 Switching Protocols\r\n”;
$response_header .= “Upgrade: websocket\r\n”;
$response_header .= “Connection: Upgrade\r\n”;
$response_header .= sprintf(
		“Sec-WebSocket-Accept: %s\r\n”,
		base64_encode( sha1($request[‘Sec-WebSocket-Key’] . 	‘258EAxxxxxxxxxxxx’, true) ) );
WebSocket 数据收发过程

游览器支持了 WebSocket,所以在写 JavaScript 的时候无需考虑数据的编码解码,服务端没有统一实现,接收的数据为二进制,需要自己做解码,发送数据需要做编码。

@doc https://tools.ietf.org/html/rfc6455#section-6
@ref https://www.cnblogs.com/farwish/p/9011107.html

三十六 Workerman实时统计

实现方式

客户端:JavaScript WebSocket API
服务器: Workerman WebSocket Server
	   Workerman Timer

三十七 Swoole聊天程序

实现方式

客户端:JavaScript WebSocket API
服务端:Swoole WebSocket Server

三十八 结尾 总结

章节回顾

多进程:pcntl 多进程 Server,进程通信,应用案例(采集)多线程:pthreads 多线程,线程锁,应用案例(查询)协程:生成器,任务调度Web并发:参数,并发调用,快速响应高级IO:3种IO长连接项目:Workerman,Swoole@代码 https://github.com/farwish/PCP

目标回顾

了解多进程、多线程、协程相关的概念能够用PHP进行基本的系统编程写出效率更高的程序,用多种方案解决问题能够使用PHP的Socket开源框架开发项目对PHP并发编程有一个全新的认识

总结

PHP 的最初的定位是 Web 层面的快速开发,在多年的专注与积累下,有明显的优势,由于 PHP 弱类型脚本语言的特点 ,所以写 Server 程序的时候更要特别小心和慎重。结合业务场景,在实践中有选择性的应用、优化。发挥想象力,扩大边界,PHP 不仅仅是 Web 应用层。为 PHP 做些贡献(库、扩展、源代码)

=======

./configure  --prefix=/usr/local/php73 --with-config-file-path=/usr/local/php73/etc --with-config-file-scan-dir=/usr/local/php73/conf.d --enable-fpm --with-fpm-user=www --with-fpm-group=www --enable-mysqlnd --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd --with-iconv-dir --with-freetype-dir=/usr/local/freetype --with-jpeg-dir --with-png-dir --with-zlib --with-libxml-dir=/usr --enable-xml --disable-rpath --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curl --enable-mbregex --enable-mbstring --enable-intl --enable-ftp --with-gd --with-openssl --with-mhash --enable-pcntl --enable-sockets --with-xmlrpc --enable-zip --without-libzip --enable-soap --with-gettext --enable-opcache --with-xsl --with-pear  --enable-maintainer-zts  --with-tsrm-pthreads

http://blog.ganyongmeng.com/?p=589

http://www.oicqzone.com/pc/2013012814814.htm

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

争当做一个有趣的人

只有非常努力,才会毫不费力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值