美文网首页程序员
网络模型 php实现select/poll epoll

网络模型 php实现select/poll epoll

作者: mafa1993 | 来源:发表于2020-04-04 20:33 被阅读0次

    网络模型

    服务器的网络服务模型

    1. 单进程阻塞式i/o模型,一次只能处理一个链接,不支持多个长链接同时处理
    2. 多进程方式,服务器收到请求,子进程处理请求,一个进程处理一个请求,或者是,先开辟一个进程池,不够用再动态开辟进程(预派生子进程),php-fpm也是这样
    • pstree 树形查看进程
    • fastcgi将请求发送给master,master分配给worker执行,一个worker只处理一个请求,处理完请求销毁
    • 资源开销大,大量请求时性能下降
    1. 多线程
    2. 单进程io阻塞复用,一个进程或者线程可以处理n个请求,性能好,稳定性差
    3. 多进程master-working io复用

    单进程阻塞

    1. 创建一个socket,绑定端口,监听端口
    2. while循环,阻塞在sccept操作上,等待下一个请求
    3. 利用fread读取客户端socket当中数据,fwrite向客户端发送响应
      简单实现
    
    //index.php
    <?php
    require "worker.php";
    //启动监听
    $worker = new Workder("tcp://0.0.0.0:8080");
    //接收到了客户端发送的消息,回调执行, $message为前端发送的数据,包括头
    $worker->onMessage = function($fd,$connnect,$message){
        //业务
        //$connect->send($fd,$message); //同一次请求不能send 两次
        //返回
        $connect->send($fd,"hello world");
    }
    $worker->runAll(); //启动
    

    worker.php

    <?php
    class Worker{
        public $onMessage; //绑定一个消息触发的回调
        private $_mainSocket; //保存socket服务端资源
        //构造函数,接受监听端口等
        public function __construct($addr){
            $this->_mainSocket = stream_socket_server($addr); //创建socket,绑定端口并监听相当于socket_create socket_bind和socket_listen
        }
        //服务启动
        public function runAll(){
            $this->listen();
        }
    
        //监听服务端发送的请求
        protected function listen(){
            while(true){
                //阻塞获取客户端请求
              $clientSocket = stream_socket_accept($this->_mainSocket);
    //从socket中读取数据,有就读取,没有就等待,得到的时文件描述符,这里是阻塞操作,等待客户端进入,程序睡眠,等到有新的请求,会被系统唤醒
                //var_dump((int)$clientSocket) 实际上获取到的式客户端文件资源描述符
                //从客户端的socket读取用户数据
                $message = fread($clientSocket,65535); //如果是浏览器请求,获取到的式请求头和请求体
                //如果有用户自定义的onMessage函数,就去执行
                //var_dump($this->onMessage);
                if(is_callable($this->onMessage)) {
                    call_user_func($this->onMessage, $clientSocket, $this, $message);
                }
                //fwrite($clientSocket,'返回的内容');
            }
        }
        //发送消息 向那个client返回,返回的内容
        public function send($fd,$message){
            //增加http头
            $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($message)."\r\n\r\n";
            $http_resonse .= $message;
            fwrite($fd,$http_resonse);
            //fclose($fd); //关闭连接,单进程的话,处理完请求直接关闭,并发处理很快,但是不适用于长连接情况,就是不进行连接的关闭
        }
    }
    

    压测

    ab -n2 -c2 -k
    //-n请求数  -c客户端数 -k keep alive,长连接
    

    单进程io复用 select/poll epoll

    一个进程连接多个请求\

    1. select 方式,遍历所有的socket,如果某个达到了可读条件就进行拿出来执行,最多监听1024个
      • 用户空间保存所有的连接
      • 内核空间遍历,得到可读的
      • 转交给用户空间处理
    2. poll方式,和select方式相似,只是没有监听个数限制,1w长连接,就要遍历1w个,即使有很多都没有信息,浪费cpu
    3. epoll,没有描述符限制,也没有遍历,当描述符状态改变时,触发事件回调,事件监听机制
      • 需要安装libevent库(linux),php需要安装event扩展,pecl install event,才能使用epoll
      • 使用swoole的event,这个比原生的更好理解
      • 将每个fd和回调添加到内核中,内核监听网卡等状态,当有请求数据是针对某个fd时,就将其执行read的回调放到就绪队列中,等待执行
      • fd在内核中以树状(红黑树)存储,方便查找

    epoll

    //Worker_epoll.php
    <?php
    class Worker
    {
        public $onMessage; //绑定一个消息触发的回调
        private $_mainSocket; //保存socket服务端资源
    //构造函数,接受监听端口等
    
        public function __construct($addr)
        {
            $this->_mainSocket = stream_socket_server($addr); //创建socket,绑定端口并监听相当于socket_create socket_bind和socket_listen
        }
    
    //服务启动
        public function runAll()
        {
            $this->listen();
        }
    
    //监听服务端发送的请求
        protected function listen()
        {
    //异步监听
            swoole_event_add($this->_mainSocket, function ($fd) { //添加一个socket到epoll的事件监听列表中
    //参数为接收到的文件描述符
    //var_dump($fd);
                $client_socket = stream_socket_accept($fd); //获取客户端内容
    //添加一个客户端socket到epoll事件监听,当socket状态法神改变的时候执行回调
    //服务器端的socket是个监听,一般不会发生状态改变,客户端的这个socket接受数据,会有状态的改变
                swoole_event_add($client_socket, function ($fd) {
                    if (feof($fd) || !is_resource($fd)) {
    //如果到了末尾或者不是资源类型,说明客户端已经关闭,就不进行后续处理
                        swoole_event_del($fd); //删除事件监听
                        fclose($fd);
                        return false;
    //或者触发onclose事件,
                    }
                    $message = fread($fd, 65535);
                    if (is_callable($this->onMessage)) {
                        call_user_func($this->onMessage, $fd, $this, $message);
                    }
                });
    });
        }
    //发送消息 向那个client返回,返回的内容
        public function send($fd, $message)
        {
    //增加http头
            $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($message) . "\r\n\r\n";
            $http_resonse .= $message;
            fwrite($fd, $http_resonse);
    //fclose($fd); //关闭连接,单进程的话,处理完请求直接关闭,并发处理很快,但是不适用于长连接情况,就是不进行连接的关闭
        }
    }
    //启动监听
    $worker = new Worker("tcp://0.0.0.0:8080");
    //接收到了客户端发送的消息,回调执行, $message为前端发送的数据,包括头
    $worker->onMessage = function($fd,$connnect,$message){
        //业务
        //$connect->send($fd,$message); //同一次请求不能send 两次
        //返回
        $connnect->send($fd,"hello world");
    };
    $worker->runAll(); //启动
    

    相关文章

      网友评论

        本文标题:网络模型 php实现select/poll epoll

        本文链接:https://www.haomeiwen.com/subject/szjsphtx.html