swoole 异步http请求 协程调用 demo

declare(strict_types=1);


/**
 * Class Http
 */
class Http
{
    private $sp = "\r\n"; //这里必须要写成双引号     private $protocol = 'HTTP/1.1';
    private $requestLine = "";
    private $requestHeader = "";
    private $requestInfo = "";
    private $fp = null;
    private $urlInfo = null;
    private $header = [];
    private $body = "";
    private static $http = null; //Http对象单例


    /**
     * Http constructor.
     */
    private function __construct()
    {
    }


    /**
     * @return Http|null
     */
    public static function getInstance()
    {
        if (self::$http === null) {
            self::$http = new Http();
        }
        return self::$http;
    }


    /**
     * @param $url
     * @return $this
     */
    public function init($url)
    {
        $this->parseUrl($url);
        $this->header['Host'] = $this->urlInfo['host'];
        return $this;
    }


    /**
     * @param array $header
     * @return Generator
     */
    public function get($header = []): Generator
    {
        $this->header = array_merge($this->header, $header);
        return $this->request('GET');
    }


    /**
     * @param array $header
     * @param array $body
     * @return bool
     */
    public function post($header = [], $body = [])
    {
        $this->header = array_merge($this->header, $header);
        if (!empty($body)) {
            $this->body = http_build_query($body);
            $this->header['Content-Type'] = 'application/x-www-form-urlencoded';
            $this->header['Content-Length'] = strlen($this->body);
        }
        return $this->request('POST');
    }


    /**
     * @param $method
     * @return bool
     */
    private function request($method)
    {
        $header = "";
        $this->requestLine = $method . ' ' . $this->urlInfo['path'] . '?' . $this->urlInfo['query'];
        foreach ($this->header as $key => $value) {
            $header .= $header == "" ? $key . ':' . $value : $this->sp . $key . ':' . $value;
        }
        $this->requestHeader = $header . $this->sp . $this->sp;
        $this->requestInfo = $this->requestLine . $this->sp . $this->requestHeader;
        if ($this->body != "") {
            $this->requestInfo .= $this->body;
        }
        $port = isset($this->urlInfo['port']) ? isset($this->urlInfo['port']) : 80;
        $this->fp = fsockopen($this->urlInfo['host'], $port, $err_no, $err_str);


        if (!$this->fp) {
            echo $err_no . '(' . $err_str . ')';
            return false;
        }
        fwrite($this->fp, $this->requestInfo);
        $ret = yield await($this->fp);
        return $ret;
    }


    /**
     * @param $url
     */
    private function parseUrl($url)
    {
        $this->urlInfo = parse_url($url);
    }
}


/**
 * Class Task
 */
class Task
{
    public $taskId;
    public $pid;
    protected $coroutine;
    protected $sendValue = null;
    protected $beforeFirstYield = true;


    /**
     * Task constructor.
     * @param $taskId
     * @param Generator $coroutine
     * @param $pid
     */
    public function __construct($taskId, Generator $coroutine, $pid)
    {
        $this->taskId = $taskId;
        $this->pid = $pid;
        $this->coroutine = $coroutine;
    }


    /**
     * @param $sendValue
     */
    public function setSendValue($sendValue)
    {
        $this->sendValue = $sendValue;
    }


    /**
     * @return mixed
     */
    public function run()
    {
        if ($this->beforeFirstYield) {
            $this->beforeFirstYield = false;
            return $this->coroutine->current();
        } else {
            $retval = $this->coroutine->send($this->sendValue);
            $this->sendValue = null;
            return $retval;
        }
    }


    /**
     * @return bool
     */
    public function isFinished()
    {
        return !$this->coroutine->valid();
    }
}




/**
 * Class Scheduler
 * @package test\io\co
 */
class Scheduler
{
    protected $maxTaskId = 0;
    public $taskMap = []; // taskId => task
    protected $taskQueue;
    public $name;


    /**
     * Scheduler constructor.
     * @param $name
     */
    public function __construct($name)
    {
        $this->taskQueue = new SplQueue();
        $this->name = $name;
    }


    /**
     * @param Generator $coroutine
     * @param int $pid
     * @return int
     */
    public function newTask(Generator $coroutine, $pid = 0)
    {
        $tid = ++$this->maxTaskId;
        $this->taskMap[$tid] = new Task($tid, $coroutine, $pid);
        $this->schedule($this->taskMap[$tid]);
        return $tid;
    }


    /**
     * @param Task $task
     */
    public function schedule(Task $task)
    {
        //var_dump('add:' . $task->get_task_id());
        $this->taskQueue->enqueue($task);
    }


    public function timer()
    {
        try {
            if (!$this->taskQueue->isEmpty()) {
                $task = $this->taskQueue->dequeue();
                //var_dump('run:' . $task->task_id);
                /** @var Task $task */
                $retval = $task->run();


                if ($retval instanceof SystemCall) {
                    //var_dump($retval->name);
                    $retval($task, $this);
                    throw new ErrorException();
                }


                if ($task->isFinished()) {
                    unset($this->taskMap[$task->taskId]);
                    throw new ErrorException();
                }


                $this->schedule($task);
                throw new ErrorException();
            } else {
                if ($this->count > 0) {
                    swoole_timer_after(300, [$this, 'timer']);
                } else {
                    unset($GLOBALS['SCHEDULER'][$this->name]);
                    //print_r(array_keys($GLOBALS['SCHEDULER']));
                }
            }
        } catch (ErrorException $e) {
           $max = 500;
            if ($GLOBALS['SCHEDULER_COUNT'] > $max) {
                swoole_timer_after((int)($GLOBALS['SCHEDULER_COUNT']/$max) * 1000, [$this, 'timer']);
            } else {
                swoole_timer_after(1, [$this, 'timer']);
            }
        }
    }


    public function run()
    {
        if (!isset($GLOBALS['SCHEDULER'])) {
            $GLOBALS['SCHEDULER'] = [];
            $GLOBALS['SCHEDULER_COUNT'] = 0;
        }


        if (isset($GLOBALS['SCHEDULER'][$this->name])) {
            throw new ErrorException();
        }


        $GLOBALS['SCHEDULER'][$this->name] = null;
        swoole_timer_after(1, [$this, 'timer']);
    }


    public $count = 0;


    /**
     * @param $tid
     * @return bool
     */
    public function killTask($tid)
    {
        if (!isset($this->taskMap[$tid])) {
            return false;
        }


        unset($this->taskMap[$tid]);


        // This is a bit ugly and could be optimized so it does not have to walk the queue,
        // but assuming that killing tasks is rather rare I won't bother with it now
        foreach ($this->taskQueue as $i => $task) {
            if ($task->get_task_id() === $tid) {
                unset($this->taskQueue[$i]);
                break;
            }
        }


        return true;
    }
}


/**
 * Class SystemCall
 * @package test\io\co
 */
class SystemCall
{
    protected $callback;
    public $name;


    /**
     * SystemCall constructor.
     * @param $name
     * @param callable $callback
     */
    public function __construct($name, callable $callback)
    {
        $this->callback = $callback;
        $this->name = $name;
    }


    /**
     * @param Task $task
     * @param Scheduler $scheduler
     * @return mixed
     */
    public function __invoke(Task $task, Scheduler $scheduler)
    {
        $callback = $this->callback;
        return $callback($task, $scheduler);
    }
}


/**
 * @return SystemCall
 */
function get_task_id(): SystemCall
{
    return new SystemCall(__FUNCTION__, function (Task $task, Scheduler $scheduler) {
        $task->setSendValue($task->taskId);
        $scheduler->schedule($task);
    });
}


/**
 * @param $fp
 * @param int $sock_type
 * @return SystemCall
 */
function await($fp, $sock_type = 0): SystemCall
{
    if ($GLOBALS['SCHEDULER_COUNT'] > 500) {
        sleep((int)($GLOBALS['SCHEDULER_COUNT']/200));
    }
    return new SystemCall(__FUNCTION__, function (Task $task, Scheduler $scheduler) use (&$fp, $sock_type) {
        $scheduler->count++;
        $GLOBALS['SCHEDULER_COUNT']++;


        $after = swoole_timer_after(1000 * 10, function () use (&$fp, $scheduler) {
            if ($fp) {
                fclose($fp);
                {
                    swoole_event_del($fp);
                    $scheduler->count--;
                    $GLOBALS['SCHEDULER_COUNT']--;
                }
            }
        });
        swoole_event_add($fp, function () use (&$fp, &$after, &$scheduler, &$task, $sock_type) {
            swoole_timer_clear($after);
            {
                swoole_event_del($fp);
                $scheduler->count--;
                $GLOBALS['SCHEDULER_COUNT']--;
            }


            try {
                if (!isset($scheduler->taskMap[$task->pid])) {
                    throw new \ErrorException();
                }


                /** @var Task $parent_task */
                $parent_task = $scheduler->taskMap[$task->pid];


                $str = '';


                switch ($sock_type) {
                    default:
                    case 0:
                        while (!feof($fp)) {
                            $str .= fread($fp, 1024);
                        }
                }


                $parent_task->setSendValue($str);
                $scheduler->schedule($parent_task);
//                    var_dump($scheduler->name . ":" .$scheduler->count);
            } catch (\ErrorException $e) {
            } finally {
                fclose($fp);
                $fp = null;
            }
        });
    });
}


/**
 * @param $name
 * @param $tid
 * @param Generator $coroutine
 * @return SystemCall
 */
function new_task($name, $tid, Generator $coroutine)
{
    return new SystemCall($name, function (Task $task, Scheduler $scheduler) use ($coroutine, $tid) {
        $task->setSendValue($scheduler->newTask($coroutine, $tid));
    });
}


/**
 * @param $tid
 * @return SystemCall
 */
function kill_ask($tid)
{
    return new SystemCall(__FUNCTION__, function (Task $task, Scheduler $scheduler) use ($tid) {
        $task->setSendValue($scheduler->killTask($tid));
        $scheduler->schedule($task);
    }
    );
}


/**
 * @param $url
 * @return Generator
 */
function level_1($url): Generator
{
    $tid = (yield get_task_id());


    $http = Http::getInstance()->init($url);
//    $time = time();
//    echo "This is task $tid time: $time.\n";
    $res = (yield new_task('http->get()', $tid, $http->get()));
    echo(__LINE__ . " $tid :" . $res . "\n");
}


/**
 * @param $url
 * @return Generator
 * @internal param $max
 */
function task($url): Generator
{
    $tid = (yield get_task_id());


    $http = Http::getInstance()->init($url);
//    $time = time();
//    echo "This is task $tid time: $time.\n";
    $res = (yield new_task('http->get()', $tid, $http->get()));
    echo(__LINE__ . " $tid :" . $res . "\n");


    $scheduler = new Scheduler("task $tid $tid $tid $tid");
    $scheduler->newTask(level_1("http://123.207.243.228/tao/?r=tao/time"));
    $scheduler->newTask(level_1("http://123.207.243.228/tao/?r=tao/time"));
    $scheduler->newTask(level_1("http://123.207.243.228/tao/?r=tao/time"));
    $scheduler->run();
}


echo(__LINE__ . ":" . time() . "\n");


$scheduler = new Scheduler('global');


$scheduler->newTask(task("http://123.207.243.228/tao/?r=tao/time"));
$scheduler->newTask(task("http://123.207.243.228/tao/?r=tao/time"));
$scheduler->newTask(task("http://123.207.243.228/tao/?r=tao/time"));


$scheduler->run();

你可能感兴趣的:(后端-php)