PHP 写 socket 服务要注意哪些地方

本文作者是 wokerman 的作者 walkor, 自己在研究 workman 的时候偶然搜索到的作者在 2014 年发表的关于 PHP 写 socket 相关的知识点介绍,觉得不错就搬砖到此,希望对研究 workman 的同学有帮助。 https://www.workerman.net/

我们知道 php-fpm 是一个 php 的 fast-cgi 管理器,它是用c写的,但是它只能提供 fastcgi 协议,一般客户端使用的是http协议,所有我们使用php-fpm的时候得在它的前面加个类似代理的东东,例如nginx。一般的情况是nginx接收http请求,把收到的http协议的数据转换成fastcgi协议的数据再转发给php的php-fpm进程,php-fpm进程会执行业务逻辑代码,然后执行相反的过程把结果最终送达浏览器。

为什么php官方不发布一个php的http管理器呢(大概是web服务器比较复杂,并且涉及静态资源)?由php-http(假如叫这个名字)进程直接接受http请求处理并返回,去掉nginx这层中转,这样就避免了nginx与php-fpm之间的协议转换及数据传递开销。

既然官方没有提供这个东西,我们自己可以写一个,当然用c写很难,们可以用php写一个,并且我们这个不能只支持http协议,不然就把自己限制死了。我们会让它支持各种协议,包括自己定义协议。所以我们这个叫一个通用点的名字,PPM(php-process-manager)。

你可能会问,用php写socket服务器靠谱么?我很负责的告诉你,绝对靠谱,不仅靠谱,某些大型互联网公司已经大规模部署过这种基于php的socket服务程序。包括我现在的公司,我也写了一个socket服务器,运行着公司最重要的业务流程,并且已经在线上跑了一阵子,运行情况非常好。

你可能会问,具体怎么做?

后面的文章我会一点一点把关键点一一写出来。首先我在这里大概说一下 php 写 socket 服务一些关注的点。

以下都是在 linux 环境下,并且使用 php-cli 模式。

首先最主要的是进程控制

1、我们不能只用一个进程去提供socket服务,那样无法充分发挥多cpu的优势(你也可以使用php线程扩展pthreads或者php协程),并且不安全(假如业务逻辑有个bug导致进程退出了,那么就没有进程再提供服务了)。

2、基于上一点,我们要有个master进程,并且有一些worker进程。master进程主要负责pcntl_wait子进程(既worker进程)的退出,如果有一个worker进程退出了,则pcntl_fork一个新的worker进程顶上去。worker进程则负责监听端口,接受请求,然后发送处理结果。

内存泄露

1、作为守护进程,最好不要有内存泄露。因为随着你的守护进程运行时间越来越长,你进程占用的内存就会越来越大,如果不做处理,内存会被耗尽。

2、其实写代码的时候稍加注意(全局变量、类的静态成员),业务逻辑都写在函数或者方法中,一般不会有内存泄露问题。

3、即使有少量的内存泄露也不必惊慌,我们可以设置每个worker进程接受多少次请求后退出,然后pcntl_fork一个干净的worker进程继续服务,就像php-fpm的pm.max_requests的设置一样。

性能如何

1、由于不用部署nginx等这样的代理程序,完全没有了nginx与php-fpm之间的协议转换及通信开销。

2、由于文件加载后就会常驻内存,后面的请求就不会再有额外的读磁盘、php请求初始化、php执行环境环境初始化、php词法语法分析、opcode编译、请求关闭这些耗时的过程,所以cpu资源占用会比在php-fpm上低很多。

3、基于上一点,一个进程中只要初始化一次数据库连接(或者memcache、redis等连接),后面就可以一直复用这个连接,大大降低了与存储服务器之间的tcp三次握手连接的及四次握手断开的通信开销。

4、测试结果显示PPM在一台pc(四核 ubuntu)每秒能够处理3w请求,内存占用(4worker7M+1master4M=32M内存),cpu占用50%左右。

以上是PPM的一个大概介绍。