swoole(3)网络服务模型(单进程阻塞、预派生子进程、单进程阻塞复用模型)

一:单进程阻塞

设计流程:

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

代码:

<?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\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下运行:

浏览器:

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

二:预派生子进程模式

设计流程:

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

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

显示规则:

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

代码:

<?php

class 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\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执行结果:

 

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

三:单进程阻塞复用模型

 设计流程:

 

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

代码:

<?php

class 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\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();

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

posted @ 2020-03-03 20:40  花花妹子。  阅读(329)  评论(0编辑  收藏  举报