swoole学习(二) ---- 手写一个预派生子进程模式的网络服务器

预派生子进程模式的引入 — php-fpm:

我们知道高并发请求时,fpm-worker不够用,nginx直接响应502,当我们达到一定并发时,最简单粗暴的办法就是增加php-fpm的进程数目,可以简单的查看一下当前的php-fpm进程数目,ps -ef |grep fpm
在这里插入图片描述
只有两个子进程
打开我们的fpm配置文件,将最大进程数设置为8个,并重启fpm
在这里插入图片描述
在这里插入图片描述

我们熟悉的php-fpm的设计就是一个预派生子进程模式,我在上一篇的博客中(单进程阻塞服务器)介绍的模型一次只能处理一个请求,在实际的业务中是比较少见的,因此我们需要加大它的并发能力,怎么加大呢?既然一个进程只能处理一个请求,那么我们就预先给他多生成N个子进程,那多个请求一起来的时候不就可以一起处理了吗?即便需要等待处理,等待的时间不也变短了吗?

知识预备:

pcntl函数(http://php.net/manual/zh/intro.pcntl.php)

设计流程:

  1. 创建一个socket,绑定服务器端口(bind),监听端口(listen);
  2. 通过pcntl_fork函数创建N个子进程
  3. 每一个子进程创建成功后都去阻塞监听新的客户端连接
  4. 当客户端连接到服务器时,其中一个子进程被唤醒,开始处理客户端请求,并 且不再接受新的连接
  5. 当此连接关闭时,子进程会释放,重新进入 监听客户端,参与处理新的连接

代码实现(windows不支持pcntl扩展,只能在linux下运行)

<?php


class Worker{
    //监听socket
    protected $socket = NULL;
    //连接事件回调
    public $onConnect = NULL;
    //接收消息事件回调
    public $onMessage = NULL;
    public $workerNum=4; //子进程个数
    public function __construct($socket_address) {
        //监听地址+端口
        $this->socket=stream_socket_server($socket_address);
    }
    public function start() {
        //获取配置文件
        $this->fork(); //创建多个子进程负责接收请求的
    }

    public function fork(){

        for ($i=0;$i<$this->workerNum;$i++){
            $pid=pcntl_fork(); //创建成功会返回子进程id
            if($pid<0){
                exit('创建失败');
            }else if($pid>0){
                //父进程空间,返回子进程id
            }else{ //返回为0子进程空间
                $this->accept();//子进程负责接收客户端请求
            }
        }
        //放在父进程空间,结束的子进程信息,阻塞状态
        $status=0;
        $pid=pcntl_wait($status);
        echo "子进程回收了:$pid".PHP_EOL;
    }

    public  function  accept(){

        //创建多个子进程阻塞接收服务端socket
        while (true){
            $clientSocket=stream_socket_accept($this->socket); //阻塞监听
            var_dump(posix_getpid());
            //触发事件的连接的回调
            if(!empty($clientSocket) && is_callable($this->onConnect)){
                call_user_func($this->onConnect,$clientSocket);
            }
            //从连接当中读取客户端的内容
            $buffer=fread($clientSocket,65535);
            //正常读取到数据,触发消息接收事件,响应内容
            if(!empty($buffer) && is_callable($this->onMessage)){
                call_user_func($this->onMessage,$clientSocket,$buffer);
            }
            fclose($clientSocket); //必须关闭,子进程不会释放不会成功拿下进入accpet
        }

    }

}


$worker = new Worker('tcp://0.0.0.0:9800');


//连接事件
$worker->onConnect = function ($fd) {
    //echo '连接事件触发',(int)$fd,PHP_EOL;
};

//消息接收
$worker->onMessage = function ($conn, $message) {
    //事件回调当中写业务逻辑
    //var_dump($conn,$message);
    $content="我收到你的请求了";
    $http_resonse = "HTTP/1.1 200 OK\r\n";
    $http_resonse .= "Content-Type: text/html;charset=UTF-8\r\n";
    $http_resonse .= "Connection: keep-alive\r\n"; //连接保持
    $http_resonse .= "Server: php socket server\r\n";
    $http_resonse .= "Content-length: ".strlen($content)."\r\n\r\n";
    $http_resonse .= $content;
    fwrite($conn, $http_resonse);
};

$worker->start(); //启动

cli模式运行
在这里插入图片描述
检查一下进程数目,ps -aux|grep service.php
在这里插入图片描述
没问题,5个进程(1个主进程,4个子进程)
测试:来一波大的,3W个请求,并发300,够大了吧?(真的够大了,服务器是1G的运行内存,子进程只设置了4个,请求时保持长连接,注意-k参数,这个很重要)
在这里插入图片描述在这里插入图片描述
完全没问题,如果单进程阻塞模式运行结果是这样的
在这里插入图片描述
50个请求2个并发都不行了有没有?太可怜了。。。

注意点:

  1. pcntl_fork是创建了一个子进程,父进程和子进程 都从fork的位置开始向下继续执行,不同的是父进程执行过程中,得到的fork返回值为子进程 号,而子进程得到的是0,所以阻塞监听的逻辑应该是在返回0的时候执行的
  2. 僵尸进程的处理,使用pcntl_wait回收子进程

还是存在缺点:预派生子进程模式虽然可以解决并发的问题,但是资源始终是有限的,我们总不能设置10w个进程来维持10w人的链接吧,尽管通过pcntl_wait不断回收,但这个模式太依赖进程的数量了;而且操作系统生成一个子进程需要进行内存复制等操作,在资源和时间上会产生一定的开销;当有大量请求时,会导致系统性能下降;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值