accept 阻塞_swoole 模型(单进程阻塞、预派生子进程、单进程阻塞复用模型)

一:单进程阻塞

设计流程:

  1. 创建一个socket,绑定端口bind,监听端口listen
  2. 进入while循环,阻塞在accept操作上,等待客户端连接进入,进入睡眠状态,直到有新的客户发起connet到服务器,accept函数返回客户端的socket
  3. 利用fread读取客户端socket当中的数据,收到数据后服务器程序进程处理,然后使用fwrite向客户端发送响应
c9b3ed8ead83ab38d073f4b78ed3e40c.png

代码:

<?php class Worker{     //监听socket     protected $socket = NULL;     //连接事件回调     public $onConnect = NULL;     //接收消息事件回调     public $onMessage = NULL;     public function __construct($socket_address) {        $this->socket=stream_socket_server($socket_address);     }     public function start() {         while (true) {             $clientSocket = stream_socket_accept($this->socket);             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);         }     } }$worker = new Worker('tcp://0.0.0.0:9810');$worker->onConnect = function ($args) {        echo "新的连接来了.{$args}.PHP_EOL";};$worker->onMessage = function ($conn, $message) {        var_dump($conn, $message);        $content="hello word qwe";    $http_resonse = "HTTP/1.1 200 OK";    $http_resonse .= "Content-Type: text/html;charset=UTF-8";    $http_resonse .= "Connection: keep-alive";    $http_resonse .= "Server: php socket server";    $http_resonse .= "Content-length: ".strlen($content)."";    $http_resonse .= $content;    fwrite($conn, $http_resonse);};$worker->start();

cli下运行:

0842bce5ff3958c8f78b0a614e6f38cd.png

浏览器:

f6893cc85af82df4e8fa4ad6481dc22b.png
缺点:一次只能处理一个连接,不支持多个连接同时处理

二:预派生子进程模式

设计流程:

c26b4d17fc9d476f13b018b22307a3ed.png
  1. 创建一个socket,绑定服务器端口(bind),监听端口(listen)
  2. 通过 pcntl_fork 函数创建N个子进程
  3. 一个子进程创建成功后都去阻塞监听新的客户端连接
  4. 客户端连接时,其中一个子进程被唤醒,处理客户端请求
  5. 请求完成后,等待主进程回收子进程 pcntl_wait

通过调用fork函数来创建子进程,会返回两个pid(主进程id、子进程id)

显示规则:

  1. 在父进程:fork函数返回子进程id
  2. 在子进程:fork函数返回0

代码:

<?phpclass Worker {    //监听socket    protected $socket = NULL;    //连接事件回调    public $onConnect = NULL;    //接收消息事件回调    public $onMessage = NULL;    public $workerNum = 10;    public function __construct($socket_address) {        $this->socket = stream_socket_server($socket_address);    }    //创建子进程    public function fork() {        for ($i = 0; $i < $this->workerNum; $i++) {            $pid = pcntl_fork();            if ($pid < 0) {                exit('创建失败');            } else if ($pid > 0) {                //父进程空间,返回子进程id            } else {                //子进程空间,返回父进程id 0                $this->accept();            }        }        $status = 0;        $pid = pcntl_wait($status);        echo "子进程" . $pid . PHP_EOL;    }    public function accept(){        while (true) {            $clientSocket = stream_socket_accept($this->socket);            var_dump("正在执行任务的pid为:".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);        }    }    public function start() {        $this->fork();    }}$worker = new Worker('tcp://0.0.0.0:9801');$worker->onConnect = function ($args) {    echo "新的连接来了.{$args}.PHP_EOL";};$worker->onMessage = function ($conn, $message) {//    var_dump($conn, $message);    $content = "hello word qwe";    $http_resonse = "HTTP/1.1 200 OK";    $http_resonse .= "Content-Type: text/html;charset=UTF-8";    $http_resonse .= "Connection: keep-alive";    $http_resonse .= "Server: php socket server";    $http_resonse .= "Content-length: " . strlen($content) . "";    $http_resonse .= $content;    fwrite($conn, $http_resonse);};$worker->start();

cli执行结果:

ec5188036e18d35080265e7cd087efa3.png
6b67c7c6b4fe6f33b0a927bfb6925faf.png
缺点:严重依赖进程的数量解决并发问题,一个客户端连接就需要占用一个进程

三:单进程阻塞复用模型

设计流程:

6504d2896536daddf0128634a1d0d700.png
  1. 保存所有的socket,通过 select 系统调用,监听socket描述符的可读事件
  2. socket在内核监控,一旦发现可读,会从内核空间传递给用户空间,通过逻辑判断是服务端socket可读,还是客户端socket可读
  3. 如果是服务端socket可读,说明有新的客户端建立,将socket保留到监听数组中
  4. 如果是客户端socket可读,说明当前已经可以去读取客户端发送过来的内容了,读取了内容,响应给客户端

代码:

<?phpclass Worker {    //监听socket    protected $socket = NULL;    //连接事件回调    public $onConnect = NULL;    //接收消息事件回调    public $onMessage = NULL;    public $workerNum = 4 ;    public $allSocket;    public function __construct($socket_address) {        $this->socket = stream_socket_server($socket_address);        stream_set_blocking($this->socket,0);        $this->allSocket[(int)$this->socket]=$this->socket;    }    public function fork() {//        for ($i = 0; $i < $this->workerNum; $i++) {//            $pid = pcntl_fork();//            if ($pid < 0) {//                exit('创建失败');//            } else if ($pid > 0) {            } else {                $this->accept();//            }//        }//        $status = 0;//        $pid = pcntl_wait($status);//        echo "子进程" . $pid . PHP_EOL;    }    public function accept(){        while (true) {            $write =$except =[];            $read= $this->allSocket;            stream_select($read,$write,$except,60);            foreach($read as $index =>$val){                if ($val == $this->socket){                    $clientSocket = stream_socket_accept($this->socket);                    var_dump(posix_getpid());                    if (!empty($clientSocket) && is_callable($this->onConnect)) {                        call_user_func($this->onConnect, $clientSocket);                    }                    $this->allSocket[(int)$clientSocket]=$clientSocket;                }else{                    $buffer = fread($val, 65535);                    if (empty($buffer)){                        if (feof($val) || is_resource($val)){                            fclose($val);                            unset($this->allSocket[(int)$val]);                            continue;                        }                    }                    if (!empty($buffer) && is_callable($this->onMessage)) {                        call_user_func($this->onMessage, $val, $buffer);                    }                }            }        }    }    public function start() {        $this->fork();    }}$worker = new Worker('tcp://0.0.0.0:9800');$worker->onConnect = function ($args) {    echo "新的连接来了.{$args}.PHP_EOL";};$worker->onMessage = function ($conn, $message) {//    var_dump($conn, $message);    $content = "hello word qwe";    $http_resonse = "HTTP/1.1 200 OK";    $http_resonse .= "Content-Type: text/html;charset=UTF-8";    $http_resonse .= "Connection: keep-alive";    $http_resonse .= "Server: php socket server";    $http_resonse .= "Content-length: " . strlen($content) . "";    $http_resonse .= $content;    fwrite($conn, $http_resonse);};$worker->start();

缺点:select模式本身缺点(循环遍历处理事件、内核空间传递数据的消耗)、单线程对于大量任务处理乏力

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值