php自定义webServer服务器

lys2018年04月08日 0条评论

php自定义webServer服务器,本例子是在linux上面运行的,多进程形式,不适用于windows

<?php
/**
 * php自定义webServer服务器
 * @author linyushan
 * @time 2017/10/12 16:49
 */

/**
 * @socket 通信的整个过程
 * @socket_create   //创建套接字
 * @socket_bind     //绑定IP和端口
 * @socket_listen   //监听相应端口
 * @socket_accept   //接收请求
 * @socket_read     //获取请求内容
 * @socket_write    //返回数据
 * @socket_close    //关闭连接
 */
class newTowMyServer
{
    /**
     * @var newMyServer
     */
    public static $master_worker;  //主进程
    public static $master_socket;  //监听的socket端口套节字 resource
    /**
     * @var array 子进程
     */
    public static $workers;
    /**
     * 所有socket套接字数组
     * @var array
     */
    public static $allSockets = [];
    private $ip;
    private $port;
    private $webroot;
    public $count = 3; //子进程数
    //将常用的MIME类型保存在一个数组中
    private $contentType = array(
        ".html" => "text/html",
        ".htm" => "text/html",
        ".xhtml" => "text/html",
        ".xml" => "text/html",
        ".php" => "text/html",
        ".java" => "text/html",
        ".jsp" => "text/html",
        ".css" => "text/css",
        ".ico" => "image/x-icon",
        ".jpg" => "application/x-jpg",
        ".jpeg" => "image/jpeg",
        ".png" => "application/x-png",
        ".gif" => "image/gif",
        ".pdf" => "application/pdf",
    );

    public function __construct($ip = "0.0.0.0", $port = 65501)
    {
        set_time_limit(0);
        $this->ip = $ip;
        $this->port = $port;
        $this->webroot = __DIR__ . '/www';
        echo "\nServer init sucess\n";
    }

    /**
     * Monitor all child processes.
     * 监视所有子进程。
     * @return void
     */
    public static function monitorWorkers()
    {
        while (1) {
            // Calls signal handlers for pending signals.调用待处理信号的信号处理程序
            pcntl_signal_dispatch();
            // Suspends execution of the current process until a child has exited, or until a signal is delivered
            //暂停执行当前进程,直到孩子退出,直到信号被传送
            $status = 0;
            $pid = pcntl_wait($status, WUNTRACED);
            var_dump($pid);
            // Calls signal handlers for pending signals again. //再次呼叫待处理信号的信号处理程序。
            pcntl_signal_dispatch();
            // If a child has already exited. 如果一个孩子已经退出了。
            if ($pid > 0) {  //退出一个子进程,则继续开启一个子进程
                unset(self::$workers[(int)$pid]);
                self::$master_worker->forkOneWorker(); //开启一个子进程
            } else {
                /* // If shutdown state and all child processes exited then master process exit.
                 if (self::$_status === self::STATUS_SHUTDOWN && !self::getAllWorkerPids()) {
                     self::exitAndClearAll();
                 }*/
            }
        }
    }

    /**
     * [读取get或post请求中的url,返回相应的文件]
     * @param  [string]
     * @return [string]
     * http头
     * method url protocols
     */
    public function request($string)
    {
        echo $string;
        $pattern = "/\s+/";

        $request = preg_split($pattern, $string);
        if (count($request) < 3)
            return "request error\n";
        $filename = $this->webroot . $request[1];
        echo "filename:" . $filename . "\n";
        $type = $this->setContentType($filename);
        if (file_exists($filename)) {
            ini_set('display_errors', 'off');
            ob_start();
            include $filename;
            $data = ob_get_clean();
            return $this->addHeader($request[2], 200, "OK", $data, $type);
        } else {
            $data = "this resource is not exists";
            return $this->addHeader($request[2], 1000, "not exists", $data, $type);
        }
    }

    private function addHeader($protocol, $state, $desc, $str, $type)
    {
        return "{$protocol} {$state} {$desc}\r\nContent-type:{$type}\r\n" . "Content-Length:" .
            strlen($str) . "\r\nServer:" . self::class . "\r\n\r\n" . $str;
    }

    private function setContentType($filename)
    {
        $type = substr($filename, strpos($filename, '.'));
        if (isset($this->contentType[$type]))
            return $this->contentType[$type];
        else
            return "text/html";
    }

    /**
     * 运行多进程模式
     */
    public function run()
    {
        @cli_set_process_title(self::class . ' master process pid=' . posix_getpid() . ' ' . __FILE__);
        self::$master_worker = $this;
        self::$master_socket = stream_socket_server("tcp://" . $this->ip . ":" . $this->port, $errno, $errstr);
        if (!self::$master_socket) {
            echo "$errstr ($errno)<br />\n";
        }
        stream_set_blocking(self::$master_socket, 0); //设置为非阻塞
        self::$allSockets[(int)self::$master_socket] = self::$master_socket;
        $i = 0;
        while ($i < $this->count) {
            $this->forkOneWorker();
            $i++;
        }
    }

    public function closeSocket($socket)
    {
        echo 'exit one socket ' . (int)$socket . "\r\n";
        unset(self::$allSockets[(int)$socket]);
        fclose($socket);
    }

    public function forkOneWorker()
    {
        $pid = pcntl_fork();
        if ($pid > 0) {
            self::$workers[(int)$pid] = $pid;
        } else {
            @cli_set_process_title(self::class . ' worker process pid=' . posix_getpid() . ' ' . __FILE__);
            while (1) {
                $write = $except = null;
                $read = self::$allSockets;
                echo 'blocking pid=' . posix_getpid() . "\r\n";
                stream_select($read, $write, $except, NULL);  //阻塞在这边,这边不判断可写的情况
                foreach ($read as $index => $socket) {
                    if ($socket === self::$master_socket) {
                        $new_socket = stream_socket_accept($socket);  //接收的新连接被别的进程处理了
                        if(empty($new_socket)){
                            continue;
                        }
                        self::$allSockets[(int)$new_socket] = $new_socket;
                    } else {
                        $string = fread($socket, 20480);
                        if ($string === '' || $string === false) {  //客户端已经退出了
                            $this->closeSocket($socket);
                            continue;
                        }
                        $data = $this->request($string);
                        $num = fwrite($socket, $data);
                        if ($num == 0) {
                            echo "WRITE ERROR:" . "\n";
                        } else {
                            echo "request already succeed\n";
                        }
                        $this->closeSocket($socket);
                    }
                }
            }
        }
    }
}

$server = new newTowMyServer();
$server->count = 1;
$server->run();
newTowMyServer:: monitorWorkers(); //监控所有进程