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