zoukankan      html  css  js  c++  java
  • 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
    ";
        $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下运行:

    浏览器:

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

    二:预派生子进程模式

    设计流程:

    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
    ";
        $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执行结果:

     

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

    三:单进程阻塞复用模型

     设计流程:

    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
    ";
        $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模式本身缺点(循环遍历处理事件、内核空间传递数据的消耗)、单线程对于大量任务处理乏力 

  • 相关阅读:
    TP5 查询mysql数据库时的find_in_set用法
    使用paginate方法分页无法判断获取的数据是否为空
    苹果电脑Mac系统如何安装Adobe Flash Player
    JavaScript Timing 事件及两种时钟写法
    JavaScript 弹出框
    JavaScript 表单验证
    fastadmin CMS等系列插件安装不成功的问题
    fastadmin中js是如何调用的
    fastadmin安装定时插件报错 ZipArchive::extractTo(): Permission denied
    今天科普一下 苹果开发者账号中:个人、公司、企业账号的区别
  • 原文地址:https://www.cnblogs.com/8013-cmf/p/12404441.html
Copyright © 2011-2022 走看看