├── example ├── wwwcounter.txt ├── taskManagerClient.php ├── www │ ├── fpm_yar_db.php │ └── fpm_yar_test.php ├── init.inc.php ├── service │ ├── Test.php │ └── Db.php ├── server_swoole.php ├── client1.php ├── benckmark │ ├── testSimple.php │ ├── mkTestData.php │ ├── testConcurrent.php │ └── lib.php ├── client_mul.php ├── server_plug.php ├── server.php ├── client_plug.php ├── client2.php ├── server1.php └── taskManagerServer.php ├── src ├── RuntimeException.php ├── encoder │ ├── EncoderInterface.php │ ├── EncoderPHP.php │ ├── EncoderJson.php │ └── EncoderMsgpack.php ├── log │ ├── LogInterface.php │ ├── File.php │ ├── TraitLog.php │ └── Log.php ├── event │ ├── InterfaceListen.php │ ├── InterfaceEventDispatcher.php │ └── TraitEventManager.php ├── Token.php ├── plug │ ├── LogSample.php │ ├── Admin.php │ └── ProfilerSample.php ├── Response.php ├── Request.php ├── Debug.php ├── Yar.php ├── Packer.php ├── Dispatcher.php ├── TaskManager.php ├── Server.php └── Protocol.php ├── tests ├── autoload.php ├── syar │ └── PackerTest.php ├── ClientTest.php └── client.php ├── .gitignore ├── composer.json └── Readme.md /example/wwwcounter.txt: -------------------------------------------------------------------------------- 1 | 2 -------------------------------------------------------------------------------- /example/taskManagerClient.php: -------------------------------------------------------------------------------- 1 | handle(); -------------------------------------------------------------------------------- /example/www/fpm_yar_test.php: -------------------------------------------------------------------------------- 1 | handle(); -------------------------------------------------------------------------------- /tests/autoload.php: -------------------------------------------------------------------------------- 1 | addPsr4("sar\\", __DIR__ . '/syar'); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Example user template template 3 | ### Example user template 4 | 5 | # IntelliJ project files 6 | .idea 7 | *.iml 8 | out 9 | gen 10 | vendor 11 | composer.lock 12 | tests/phpunit.phar -------------------------------------------------------------------------------- /src/encoder/EncoderInterface.php: -------------------------------------------------------------------------------- 1 | addPsr4('syar\\example\\service\\', __DIR__ . '/service'); -------------------------------------------------------------------------------- /src/log/LogInterface.php: -------------------------------------------------------------------------------- 1 | set([ 5 | 'http_parse_post' => false 6 | ]); 7 | 8 | $http->on('request', function ($request, $response) { 9 | echo $request->rawContent(); 10 | $response->end("

Hello Swoole. #".rand(1000, 9999)."

"); 11 | }); 12 | 13 | $http->start(); -------------------------------------------------------------------------------- /src/event/InterfaceEventDispatcher.php: -------------------------------------------------------------------------------- 1 | getName("tester"); 10 | $age = $client->getAge(); 11 | 12 | // 13 | echo "
";
14 | var_dump($name);
15 | var_dump($age);
16 | 


--------------------------------------------------------------------------------
/example/benckmark/testSimple.php:
--------------------------------------------------------------------------------
 1 |  [
10 | 		'api' => '/test',
11 | 		'method' => 'getAge',
12 | 		'params' => []
13 | 	],
14 | 	'name' => [
15 | 		'api' => '/test',
16 | 		'method' => 'getName',
17 | 		'params' => ['tester']
18 | 	]
19 | ];
20 | $rs = $client->calls($requests);
21 | 
22 | //
23 | echo "
";
24 | var_dump($rs);
25 | 


--------------------------------------------------------------------------------
/example/server_plug.php:
--------------------------------------------------------------------------------
 1 | setLogger(new Log());
12 | $server->getProtocol()->getProcessor()->setNs($apiNs);
13 | 
14 | // add plug
15 | $server->addPlug(new \syar\plug\Admin());
16 | $server->addPlug(new \syar\plug\LogSample(), false);
17 | 
18 | // reg task for log
19 | $server->getTaskManager()->regTask('log', function($log){
20 |     echo $log;
21 | });
22 | 
23 | $server->run();


--------------------------------------------------------------------------------
/example/server.php:
--------------------------------------------------------------------------------
 1 | setLogger(new Log());
11 | $service = new \syar\example\service\Test();
12 | $server->setDispatcher(function(\syar\Token $token, $isDocument) use ($service){
13 |     if(!$isDocument){
14 |         $method = $token->getMethod();
15 |         $params = $token->getArgs();
16 |         $value = call_user_func_array(array($service, $method), $params);
17 |     } else {
18 | 	    $value = "

Yar api document

"; 19 | } 20 | return $value; 21 | }); 22 | 23 | $server->run(['max_request' => 10000]); -------------------------------------------------------------------------------- /example/client_plug.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'api' => '/test', 11 | 'method' => 'getAge', 12 | 'params' => [] 13 | ], 14 | 'name' => [ 15 | 'api' => '/test', 16 | 'method' => 'getName', 17 | 'params' => ['tester'] 18 | ]]; 19 | $rs = $client->calls($requests); 20 | 21 | // status 22 | $status = file_get_contents('http://127.0.0.1:5604/admin/status'); 23 | //$closed = file_get_contents('http://127.0.0.1:5604/admin/stop'); 24 | // 25 | echo "
";
26 | var_dump($rs);
27 | var_dump($status);
28 | 


--------------------------------------------------------------------------------
/example/client2.php:
--------------------------------------------------------------------------------
 1 | ";
28 | var_dump($data);


--------------------------------------------------------------------------------
/example/benckmark/mkTestData.php:
--------------------------------------------------------------------------------
 1 | run($c, function(swoole_process $worker) use($step){
14 |         echo "Worker {$worker->pid} start\n";
15 |         $dbo = \syar\example\service\Db::getDb();
16 |         for($x = 0; $x < $step; $x++){
17 |             $name = 'title_' . ($x);
18 |             $age = rand(10, 50);
19 |             $sex = rand(1, 2);
20 |             $sql = "insert tmp_1(title, sex, age) value ('{$name}', {$sex}, {$age})";
21 |             $dbo->query($sql);
22 |         }
23 |         $worker->exit(0);
24 |     });
25 | }
26 | 
27 | // 50进程生成1万测试数据
28 | insertTestData(50, 10000);


--------------------------------------------------------------------------------
/example/benckmark/testConcurrent.php:
--------------------------------------------------------------------------------
 1 | =5.4.16",
29 |         "ext-swoole": ">=1.8.8",
30 |         "ext-msgpack": "*"
31 |     },
32 | 
33 |     "require-dev" : {
34 |     },
35 | 
36 |     "config" : {
37 |         "secure-http" : false
38 |     }
39 | }
40 | 


--------------------------------------------------------------------------------
/example/server1.php:
--------------------------------------------------------------------------------
 1 | setLogger(new Log());
12 | $server->getProtocol()->getProcessor()->setNs($apiNs);
13 | $server->run([
14 |     'max_connection' => 20480,
15 |     'worker_num' => 48,
16 |     'task_worker_num' => 16,
17 | ]);
18 | 
19 | /**
20 |  * @see http://wiki.swoole.com/wiki/page/p-server/sysctl.html
21 | 
22 | 内核参数调整
23 | ulimit -n 655350
24 | 
25 | # time_wait
26 | sysctl net.ipv4.tcp_tw_recycle=1
27 | sysctl net.ipv4.tcp_tw_reuse=1
28 | 
29 | # 进程间通信
30 | sysctl net.unix.max_dgram_qlen=100
31 | 
32 | # socket buffer
33 | sysctl net.core.wmem_default=8388608
34 | sysctl net.core.rmem_default=8388608
35 | sysctl net.core.rmem_default=16777216
36 | sysctl net.core.wmem_default=16777216
37 | 
38 | # 消息队列
39 | sysctl kernel.msgmnb=4203520
40 | sysctl kernel.msgmni=64
41 | sysctl kernel.msgmax=8192
42 |  */


--------------------------------------------------------------------------------
/src/log/File.php:
--------------------------------------------------------------------------------
 1 | file = $file;
16 |     }
17 | 
18 |     /**
19 |      * @param string $file
20 |      */
21 |     public function setFile($file) {
22 |         $this->file = $file;
23 |     }
24 | 
25 | 	protected function getFile($type){
26 | 		return $this->file;
27 | 	}
28 | 
29 | 	protected function dispose($message, $type = 'info'){
30 | 		file_put_contents(
31 | 			$this->getFile($type),
32 | 			$this->format($message, $type),
33 | 			FILE_APPEND
34 | 		);
35 | 	}
36 | 
37 |     /**
38 |      * @return bool|int
39 |      */
40 |     public function logrotate(){
41 |         if(file_exists($this->file)){
42 |             $size = filesize($this->file);
43 |             if($size > $this->maxSize){
44 |                 return file_put_contents($this->file, '');
45 |             }
46 |         }
47 |         return true;
48 |     }
49 | }


--------------------------------------------------------------------------------
/src/event/TraitEventManager.php:
--------------------------------------------------------------------------------
 1 | bind($this);
18 |     }
19 | 
20 |     public function on($name, $callback){
21 |         $this->__ECallback[$name][] = $callback;
22 |     }
23 | 
24 |     public function off($name = null) {
25 |         if(!$name){
26 |             $this->__ECallback = [];
27 |         }else{
28 |             unset($this->__ECallback[$name]);
29 |         }
30 |     }
31 | 
32 |     public function trigger($event) {
33 |         $args = func_get_args();
34 |         array_shift($args);
35 | 
36 |         foreach($this->__ECallback[$event] as $callback){
37 |             if(true === call_user_func_array($callback, $args)){
38 |                 break;
39 |             }
40 |         }
41 |     }
42 | 
43 |     public function hasListener($name){
44 |         return isset($this->__ECallback[$name]);
45 |     }
46 | }


--------------------------------------------------------------------------------
/src/log/TraitLog.php:
--------------------------------------------------------------------------------
 1 | logger = $logger;
23 |         return $this;
24 |     }
25 | 
26 |     /**
27 |      * @return LogInterface|Monolog
28 |      */
29 |     public function getLogger() {
30 |         return isset($this->logger) ? $this->logger : null;
31 |     }
32 | 
33 |     /**
34 |      * @param $message
35 |      * @param string $type
36 |      * @param array $context
37 |      */
38 |     protected function log($message, $type = 'info', $context = []){
39 |         if(!isset($this->logger)){
40 |             return;
41 |         }
42 | 
43 |         if($this->logger instanceof LogInterface) {
44 |             $this->logger->log($message, $type);
45 |         } else {
46 |             $this->logger->log($type, $message, $context);
47 |         }
48 |     }
49 | }


--------------------------------------------------------------------------------
/example/service/Db.php:
--------------------------------------------------------------------------------
 1 |  true]);
27 |         return self::$pdo;
28 |     }
29 | 
30 |     public function getInfo($id){
31 |         $sql = "select * from tmp_1 where id=" . intval($id);
32 |         //echo $sql . "\n";
33 |         $set = self::getDb()->query($sql);
34 |         $info = $set->fetch(PDO::FETCH_ASSOC);
35 |         return $info;
36 |     }
37 | 
38 |     public function getList($start = 0, $limit = 10){
39 |         $start = intval($start);
40 |         $limit = intval($limit);
41 |         $sql = "select * from tmp_1 where 1 limit {$limit} offset {$start}";
42 |         $set = self::getDb()->query($sql);
43 |         $list = $set->fetchAll(PDO::FETCH_ASSOC);
44 |         return $list;
45 |     }
46 | }


--------------------------------------------------------------------------------
/example/taskManagerServer.php:
--------------------------------------------------------------------------------
 1 | set([
 8 | 	'task_worker_num' => 2,
 9 | 	'http_parse_post' => false,
10 | ]);
11 | 
12 | $taskManager = new TaskManager($http);
13 | 
14 | $taskManager->regTask('test', function($hello = 'hello world'){
15 | 	echo "run task test:\n";
16 | 	echo $hello . "\n";
17 | 	throw new Exception("has error");
18 | });
19 | 
20 | $taskManager->regTask('send_mail', function($address = 'email@address', $content = "mail body"){
21 | 	echo "run task send_mail:\n";
22 | 	echo $address . "\n";
23 | 	echo $content . "\n";
24 | 	return true;
25 | });
26 | 
27 | $http->on('request', function ($request, $response) use ($taskManager) {
28 |     echo "\n";
29 |     $taskManager->doTask('test', ['Hello world']);
30 |     echo "---------\n";
31 |     $taskManager->doTasks([
32 |         ['test', "Hello world 1"],
33 |         ['send_mail', ['test@address', 'mail for test']]
34 |         ]);
35 |     echo "---------\n";
36 |     $taskManager->doTasksAsync([
37 |         ['test', "Hello world 1"],
38 |         ['send_mail', ['test@address', 'mail for test']]
39 |     ], function($rs){
40 |         var_dump($rs);
41 |     });
42 | 	$response->end("

Hello Swoole. #".rand(1000, 9999)."

"); 43 | }); 44 | 45 | $http->start(); -------------------------------------------------------------------------------- /src/Token.php: -------------------------------------------------------------------------------- 1 | class = $class; 24 | $this->method = $method; 25 | $this->args = $args; 26 | $this->options = $options; 27 | } 28 | 29 | /** 30 | * @return mixed 31 | */ 32 | public function getClass(){ 33 | return trim($this->class, '\\/ '); 34 | } 35 | 36 | /** 37 | * @return mixed 38 | */ 39 | public function getMethod(){ 40 | return $this->method; 41 | } 42 | 43 | /** 44 | * @return mixed 45 | */ 46 | public function getArgs(){ 47 | return $this->args; 48 | } 49 | 50 | /** 51 | * @param $key 52 | * @param null $def 53 | * @return mixed|array 54 | */ 55 | public function getOption($key = null, $def = null){ 56 | if(!$key){ 57 | return $this->options; 58 | } 59 | return isset($this->options[$key]) ? $this->options[$key] : $def; 60 | } 61 | 62 | /** 63 | * /API_PATH?args 64 | * /?api=API_PATH 65 | * @return string 66 | */ 67 | public function getApi(){ 68 | $api = $this->class ?: $this->getOption('api'); 69 | $api = str_replace('/', ".", $api); 70 | if(!$api){ 71 | return ''; 72 | } 73 | 74 | if($this->method){ 75 | $api .= "." . $this->method; 76 | } 77 | return $api; 78 | } 79 | } -------------------------------------------------------------------------------- /src/plug/LogSample.php: -------------------------------------------------------------------------------- 1 | start = $this->_time(); 26 | } 27 | 28 | protected $start; 29 | 30 | /** 31 | * @param mixed $rs 32 | * @param Token $token 33 | * @param Protocol $protocol 34 | */ 35 | public function onRequest2($rs, $token, $protocol){ 36 | $message = $token->getClass() 37 | . "({$token->getMethod()})" . "\t" 38 | . round($this->_time() - $this->start, 5) . "\t" 39 | . json_encode($token->getArgs()); 40 | $log = date("Y-m-d H:i:s") . " " . $message . "\n"; 41 | 42 | $taskManager = $protocol->server->getTaskManager(); 43 | if($taskManager->has('log')){ 44 | $taskManager->doTask('log', [$log]); 45 | } else { 46 | echo $log; 47 | } 48 | } 49 | 50 | /** 51 | * @param Dispatcher $em 52 | */ 53 | function bind($em){ 54 | $em->on($em::EVENT_REQUEST_BEFORE, array($this, 'onRequest1')); 55 | $em->on($em::EVENT_REQUEST_AFTER, array($this, 'onRequest2')); 56 | } 57 | } -------------------------------------------------------------------------------- /tests/syar/PackerTest.php: -------------------------------------------------------------------------------- 1 | unpack($this->getData()); 14 | 15 | $this->assertEquals($yar->packer['packName'], 'MSGPACK'); 16 | $this->assertEquals($yar->getRequestMethod(), 'add'); 17 | $this->assertEquals($yar->getRequestParams(), [1, 2]); 18 | } 19 | 20 | function testPack(){ 21 | $packer = new Packer(); 22 | $requestData = $this->getData(); 23 | $yar = $packer->unpack($requestData); 24 | 25 | $rs = ['test']; 26 | $yar->setReturnValue($rs); 27 | 28 | $responseData = $this->getData(2, $rs); 29 | $packData = $packer->pack($yar); 30 | 31 | $this->assertEquals($packData, $responseData); 32 | } 33 | 34 | protected function getData($type = 1, $rs = []){ 35 | $data = [ 36 | 'i' => '3594717145', 37 | 'm' => 'add', 38 | 'p' => [1, 2], 39 | ]; 40 | if($type == 2){ 41 | $data = [ 42 | 'r' => $rs, 43 | 'i' => $data['i'], 44 | 's' => 0 45 | ]; 46 | } 47 | 48 | $header = [ 49 | 'id' => 12903494, 50 | 'Version' => 0, 51 | 'MagicNum' => 0x80DFEC60, 52 | 'Reserved' => 0, 53 | 'Provider' => '', 54 | 'Token' => '', 55 | 'BodyLen' => 27, 56 | ]; 57 | $data = msgpack_pack($data); 58 | $header['BodyLen'] = strlen($data) + 8; 59 | $header = pack('NnNNa32a32N', 60 | $header['id'], 61 | $header['Version'], 62 | $header['MagicNum'], 63 | $header['Reserved'], 64 | $header['Provider'], 65 | $header['Token'], 66 | $header['BodyLen'] 67 | ); 68 | return $header . "MSGPACK " . $data; 69 | } 70 | } -------------------------------------------------------------------------------- /tests/ClientTest.php: -------------------------------------------------------------------------------- 1 | getUrl($api)); 15 | } 16 | 17 | protected function getUrl($api = 'test'){ 18 | return $url = "http://127.0.0.1:5604/{$api}"; 19 | } 20 | 21 | function testRpc1(){ 22 | $client = $this->getYarClient(); 23 | $name = $client->getName("tester"); 24 | $age = $client->getAge(); 25 | 26 | $this->assertEquals($name, 'tester Hello'); 27 | $this->assertEquals($age, 20); 28 | } 29 | 30 | function testConcurrentClient(){ 31 | $url = $this->getUrl(); 32 | 33 | $data = []; 34 | Yar_Concurrent_Client::call($url, "getName", ['tester'], 35 | function($rs) use (& $data){ 36 | $data['name'] = $rs; 37 | } 38 | ); 39 | Yar_Concurrent_Client::call($url, "getAge", [], 40 | function($rs) use (& $data){ 41 | $data['age'] = $rs; 42 | } 43 | ); 44 | Yar_Concurrent_Client::loop(); 45 | 46 | $this->assertEquals($data['name'], 'tester Hello'); 47 | $this->assertEquals($data['age'], 20); 48 | } 49 | 50 | function testBatch(){ 51 | $client = $this->getYarClient('multiple'); 52 | $requests = [ 53 | 'age' => [ 54 | 'api' => '/test', 55 | 'method' => 'getAge', 56 | 'params' => [] 57 | ], 58 | 'name' => [ 59 | 'api' => '/test', 60 | 'method' => 'getName', 61 | 'params' => ['tester'] 62 | ]]; 63 | $rs = $client->calls($requests); 64 | $this->assertEquals($rs['name'], 'tester Hello'); 65 | $this->assertEquals($rs['age'], 20); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/plug/Admin.php: -------------------------------------------------------------------------------- 1 | getPath(); 25 | if(strpos($path, $this->pathPrefix) !== 0){ 26 | return false; 27 | } 28 | 29 | $server = $protocol->server->getSwooleServer(); 30 | $cmd = str_replace($this->pathPrefix, '', $path); 31 | switch($cmd){ 32 | case "status" : 33 | $rs = $server->stats(); 34 | break; 35 | case "reload" : 36 | $rs = $server->reload(); 37 | break; 38 | case "stop" : 39 | $rs = $server->shutdown(); 40 | break; 41 | default : 42 | $rs = "invalid command"; 43 | } 44 | 45 | $rs = var_export($rs, true); 46 | if($request->get('pretty')){ 47 | $rs = "
\n{$rs}
"; 48 | } 49 | $response->body = $rs; 50 | $response->send(); 51 | 52 | return true; 53 | } 54 | 55 | /** 56 | * @param Protocol $em 57 | */ 58 | function bind($em){ 59 | $em->on($em::EVENT_REQUEST_BEFORE, array($this, 'onRequest')); 60 | } 61 | } -------------------------------------------------------------------------------- /src/plug/ProfilerSample.php: -------------------------------------------------------------------------------- 1 | counter = self::$counter; 33 | $this->queue[self::$counter] = [ 34 | 'start' => $this->_time(), 35 | 'api' => $request->getPath(), 36 | 'method' => $request->yar ? $request->yar->getRequestMethod() : '', 37 | ]; 38 | $this->queueLen++; 39 | } 40 | 41 | function onRequest2($request){ 42 | $index = $request->counter; 43 | $this->queue[$index]['time'] = $this->_time() - $this->queue[$index]['start']; 44 | 45 | if($this->queueLen >= $this->queueItemMax){ 46 | var_dump($this->queue); 47 | $this->queueLen = 0; 48 | $this->queue = []; 49 | } 50 | } 51 | 52 | /** 53 | * @param Protocol $em 54 | */ 55 | function bind($em){ 56 | $em->on($em::EVENT_REQUEST_BEFORE, array($this, 'onRequest1')); 57 | $em->on($em::EVENT_RESPONSE_AFTER, array($this, 'onRequest2')); 58 | } 59 | } -------------------------------------------------------------------------------- /src/Response.php: -------------------------------------------------------------------------------- 1 | response = $response; 20 | } 21 | 22 | function __call($name, $arguments) { 23 | return call_user_func_array(array($this->response, $name), $arguments); 24 | } 25 | 26 | /** 27 | * 设置Http状态 28 | * @param $code 29 | */ 30 | function setHttpStatus($code) { 31 | $this->response->status($code); 32 | } 33 | 34 | /** 35 | * 设置Http头信息 36 | * @param $key 37 | * @param $value 38 | */ 39 | function setHeader($key, $value) { 40 | $this->response->header($key, $value); 41 | } 42 | 43 | function gzip($level = 1){ 44 | $this->response->gzip($level); 45 | } 46 | 47 | public $body; 48 | 49 | /** 50 | * 添加http header 51 | * @param $header 52 | */ 53 | function addHeaders(array $header) { 54 | foreach($header as $key => $value) 55 | $this->response->header($key, $value); 56 | } 57 | 58 | function noCache() { 59 | $this->response->header('CacheListener-Control', 60 | 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'); 61 | $this->response->header('Pragma','no-cache'); 62 | } 63 | 64 | protected $isSend = false; 65 | 66 | function send(){ 67 | $this->isSend = true; 68 | $this->response->end($this->body); 69 | } 70 | 71 | public function isSend(){ 72 | return $this->isSend; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/log/Log.php: -------------------------------------------------------------------------------- 1 | log($message, $type); 32 | } 33 | 34 | /** 35 | * @param mixed $mask 36 | */ 37 | public function setMask($mask) { 38 | $this->mask = $mask; 39 | } 40 | 41 | protected function format($message, $type){ 42 | if(!$message){ 43 | return "\n"; 44 | } 45 | return "[{$type} " . microtime(true) . '] - ' . var_export($message, true) . PHP_EOL; 46 | } 47 | 48 | protected function getMask($type){ 49 | $types = [ 50 | 'info' => self::INFO, 51 | 'warning' => self::WARNING, 52 | 'error' => self::ERROR, 53 | 'debug' => self::DEBUG, 54 | ]; 55 | return isset($types[$type]) ? $types[$type] : self::INFO; 56 | } 57 | 58 | public function log($message, $type = 'info'){ 59 | if($this->mask & $this->getMask($type)){ 60 | $this->dispose($message, $type); 61 | } 62 | return $this; 63 | } 64 | 65 | protected function dispose($message, $type = 'info'){ 66 | echo $this->format($message, $type); 67 | } 68 | 69 | public function logrotate() { 70 | 71 | } 72 | } -------------------------------------------------------------------------------- /src/Request.php: -------------------------------------------------------------------------------- 1 | request = $request; 27 | } 28 | 29 | function __call($name, $arguments) { 30 | return call_user_func_array(array($this->request, $name), $arguments); 31 | } 32 | 33 | function getHeader($key = null, $def = null){ 34 | if(!$key){ 35 | return $this->request->header; 36 | } 37 | return isset($this->request->header[$key]) ? $this->request->header[$key] : $def; 38 | } 39 | 40 | function getUri(){ 41 | return $this->request->server['request_uri']; 42 | } 43 | 44 | function getPath(){ 45 | return $this->request->server['path_info']; 46 | } 47 | 48 | function getIp() { 49 | return $this->request->server['remote_addr']; 50 | } 51 | 52 | function getMethod(){ 53 | return $this->request->server['request_method']; 54 | } 55 | 56 | public function isPost(){ 57 | return $this->request->server['request_method'] == 'POST'; 58 | } 59 | 60 | function getPost(){ 61 | return $this->request->post; 62 | } 63 | 64 | function getGet(){ 65 | return isset($this->request->get) ? $this->request->get : []; 66 | } 67 | 68 | function get($key){ 69 | return isset($this->request->get[$key]) ? $this->request->get[$key] : null; 70 | } 71 | 72 | function getYarMethod() { 73 | return $this->yar->getRequestMethod(); 74 | } 75 | 76 | public function getYarParams(){ 77 | return $this->yar->getRequestParams(); 78 | } 79 | } -------------------------------------------------------------------------------- /src/Debug.php: -------------------------------------------------------------------------------- 1 | $t) { 48 | if (!isset($t['file'])) { 49 | $t['file'] = 'unknown'; 50 | } else { 51 | $t['file'] = self::getFilePath($t['file']); 52 | } 53 | if (!isset($t['line'])) { 54 | $t['line'] = 0; 55 | } 56 | if (!isset($t['function'])) { 57 | $t['function'] = 'unknown'; 58 | } 59 | $log .= "#$i {$t['file']}({$t['line']}): "; 60 | if (isset($t['object']) && is_object($t['object'])){ 61 | $log .= get_class($t['object']) . '->'; 62 | } 63 | $log .= "{$t['function']}()\n"; 64 | } 65 | 66 | if($start && count($log) > $start){ 67 | $log = array_slice($log, $start); 68 | } 69 | 70 | return $log; 71 | } 72 | 73 | protected static function getFilePath($file){ 74 | if(defined('PATH_ROOT')){ 75 | return str_replace(PATH_ROOT, '', $file); 76 | } else { 77 | $root = preg_replace('/^(.+?)\/vender/', '$0', __FILE__); 78 | return str_replace($root, '', $file); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/Yar.php: -------------------------------------------------------------------------------- 1 | '', //transaction id 36 | "m" => '', //the method which being called 37 | "p" => array(), //parameters 38 | ) 39 | */ 40 | public $request; 41 | 42 | /** 43 | * @var array 44 | * array( 45 | "i" => '', 46 | "s" => '', //status 0 == success 47 | "r" => '', //return value 48 | "o" => '', //output 49 | "e" => '', //error or exception 50 | ) 51 | */ 52 | public $response; 53 | 54 | function isError(){ 55 | return isset($this->response['e']); 56 | } 57 | 58 | function getResponse(){ 59 | if(isset($this->request)){ 60 | $this->response['i'] = $this->request['i']; 61 | } 62 | 63 | if(!isset($this->response['s'])){ 64 | $this->response['s'] = 0; 65 | } 66 | 67 | return $this->response; 68 | } 69 | 70 | function setReturnValue($value){ 71 | $this->response['r'] = $value; 72 | } 73 | 74 | function getRequestMethod(){ 75 | return $this->request['m']; 76 | } 77 | 78 | function getRequestParams(){ 79 | return $this->request['p']; 80 | } 81 | 82 | /** 83 | * @param string $message 84 | * @param int $status 85 | * @param string $output 86 | */ 87 | function setError($message, $status = 1, $output = ''){ 88 | $this->response['s'] = $status; 89 | $this->response['e'] = $message; 90 | $this->response['o'] = $output; 91 | } 92 | 93 | function getError(){ 94 | return $this->response['e']; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/client.php: -------------------------------------------------------------------------------- 1 | '3594717145', 46 | 'm' => 'getAge1', 47 | 'p' => [1, 2], 48 | ]; 49 | 50 | $header = [ 51 | 'id' => 12903494, 52 | 'Version' => 0, 53 | 'MagicNum' => 0x80DFEC60, 54 | 'Reserved' => 0, 55 | 'Provider' => '', 56 | 'Token' => '', 57 | 'BodyLen' => 27, 58 | ]; 59 | 60 | $data = serialize($request); 61 | $header['BodyLen'] = strlen($data) + 8; 62 | 63 | $header = pack('NnNNa32a32N', 64 | $header['id'], 65 | $header['Version'], 66 | $header['MagicNum'], 67 | $header['Reserved'], 68 | $header['Provider'], 69 | $header['Token'], 70 | $header['BodyLen'] 71 | ); 72 | 73 | $data = $header . "PHP\x0\x0\x0\x0\x0" . $data; 74 | 75 | echo post($url, $data, [ 76 | "User-Agent: PHP Yar Rpc-1.2.4", 77 | "Content-Type: application/x-www-form-urlencoded" 78 | ]); 79 | echo "\n"; -------------------------------------------------------------------------------- /src/Packer.php: -------------------------------------------------------------------------------- 1 | response['e'] = "Invalid request"; 38 | return $yar; 39 | } 40 | 41 | $header = substr($data, 0, 82); 42 | $header = unpack(self::HEADER_STRUCT, $header); 43 | 44 | if(strlen($data) - 82 != $header['BodyLen']){ 45 | $yar->response['e'] = "Invalid body"; 46 | return $yar; 47 | } 48 | 49 | $packName = substr($data, 82, 8); 50 | $yar->packer['packData'] = $packName; 51 | 52 | $packName = $this->getPackName($packName); 53 | $yar->packer['packName'] = $packName; 54 | 55 | $encoder = $this->getEncoder($packName); 56 | $request = $encoder->decode(substr($data, 90)); 57 | 58 | $yar->header = $header; 59 | $yar->request = $request; 60 | $yar->packer['encoder'] = $encoder; 61 | return $yar; 62 | } 63 | 64 | protected function getPackName($data){ 65 | foreach(self::$packagers as $packer){ 66 | if(strncasecmp($packer, $data, strlen($packer)) == 0){ 67 | return $packer; 68 | } 69 | } 70 | return self::ENCODE_PHP; 71 | } 72 | 73 | /** 74 | * @param Yar $yar 75 | * @return string 76 | */ 77 | function pack($yar){ 78 | /** @var EncoderInterface $packer */ 79 | $packer = $yar->packer['encoder']; 80 | $data = $packer->encode($yar->getResponse()); 81 | 82 | $header =& $yar->header; 83 | $header['BodyLen'] = strlen($data) + 8; 84 | 85 | return pack(self::HEADER_PACK, 86 | $header['id'], 87 | $header['Version'], 88 | $header['MagicNum'], 89 | $header['Reserved'], 90 | $header['Provider'], 91 | $header['Token'], 92 | $header['BodyLen'] 93 | ) 94 | . $yar->packer['packData'] 95 | . $data 96 | ; 97 | } 98 | 99 | /** 100 | * @param string $type 101 | * @return EncoderJson|EncoderMsgpack|EncoderPHP 102 | */ 103 | protected function getEncoder($type = self::ENCODE_JSON){ 104 | if(isset(self::$encoder[$type])){ 105 | return self::$encoder[$type]; 106 | } 107 | 108 | switch($type){ 109 | case self::ENCODE_MSGPACK : 110 | $instance = new EncoderMsgpack(); 111 | break; 112 | 113 | case self::ENCODE_JSON : 114 | $instance = new EncoderJson(); 115 | break; 116 | 117 | default : 118 | $instance = new EncoderPHP(); 119 | } 120 | 121 | self::$encoder[$type] = $instance; 122 | return $instance; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Dispatcher.php: -------------------------------------------------------------------------------- 1 | ns = $ns; 27 | return $this; 28 | } 29 | 30 | /** 31 | * @param $api 32 | * @param $class 33 | */ 34 | public function regClass($api, $class){ 35 | $this->classMap[$api] = $class; 36 | return; 37 | } 38 | 39 | protected $instances = []; 40 | protected function getClass($api){ 41 | $api = trim($api, '/'); 42 | if(isset($this->instances[$api])){ 43 | return $this->instances[$api]; 44 | } 45 | 46 | if(isset($this->classMap[$api])){ 47 | $class = $this->classMap[$api]; 48 | } else { 49 | if(strpos($api, "/") !== false){ 50 | $classPrefix = str_replace('/', '\\', dirname($api)) . "\\"; 51 | $className = ucfirst(basename($api)); 52 | } else { 53 | $classPrefix = ''; 54 | $className = ucfirst($api); 55 | } 56 | $class = $this->ns . $classPrefix . $className; 57 | } 58 | 59 | if(is_string($class)){ 60 | if(!class_exists($class)){ 61 | throw(new Exception("Invalid class({$class})")); 62 | } 63 | $this->instances[$api] = new $class(); 64 | } else { 65 | $this->instances[$api] = $class; 66 | } 67 | 68 | return $this->instances[$api]; 69 | } 70 | 71 | 72 | public $canCache = false; 73 | private $caches; 74 | 75 | /** 76 | * @param Token $request [path, call_method, method_params, $_GET] 77 | * @param $protocol 78 | * @return mixed 79 | * @throws Exception 80 | */ 81 | protected function process($request, $protocol){ 82 | if($this->canCache){ 83 | $cacheId = serialize([$request->getApi(), $request->getArgs()]); 84 | $cacheId = md5($cacheId); 85 | if(isset($this->caches[$cacheId])){ 86 | return $this->caches[$cacheId]; 87 | } 88 | } 89 | 90 | $class = $this->getClass($request->getClass()); 91 | $method = $request->getMethod(); 92 | if(!method_exists($class, $method)){ 93 | throw(new Exception("Invalid method")); 94 | } 95 | 96 | if(method_exists($class, 'setProtocol')){ 97 | $class->setProtocol($protocol); 98 | } 99 | 100 | $rs = call_user_func_array(array($class, $method), $request->getArgs()); 101 | if(isset($cacheId)){ 102 | $this->caches[$cacheId] = $rs; 103 | } 104 | return $rs; 105 | } 106 | 107 | protected function getDocument($request){ 108 | return "

Swoole yar document

"; 109 | } 110 | 111 | /** 112 | * @param Token $token 113 | * @param $protocol 114 | * @param $isDocument 115 | * @return mixed|string 116 | * @throws Exception 117 | */ 118 | public function __invoke($token, $isDocument, $protocol) { 119 | if(!$isDocument) { 120 | if($this->hasListener(self::EVENT_REQUEST_BEFORE)){ 121 | $this->trigger(self::EVENT_REQUEST_BEFORE, $token, $protocol); 122 | } 123 | 124 | $rs = $this->process($token, $protocol); 125 | 126 | if($this->hasListener(self::EVENT_REQUEST_AFTER)){ 127 | $this->trigger(self::EVENT_REQUEST_AFTER, $rs, $token, $protocol); 128 | } 129 | return $rs; 130 | } else { 131 | return $this->getDocument($token); 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## 为何用swoole来实现 Yar server 2 | * 提升Yar服务效率 3 | * 提升Yar服务稳定性 4 | * 学习swoole, yar(在此感谢laruence,rango及swoole开发团队) 5 | 6 | ## Requirements 7 | 1. php5.4+ 8 | 1. ext-swoole 1.8.8+ 9 | 1. ext-msgpack 如果yar使用msgpack编码方式 10 | 11 | ## Installation 12 | 13 | ``` 14 | composer require stcer/syar 15 | ``` 16 | 17 | ## Example 18 | **服务端** 19 | example\server.php 20 | 21 | ``` 22 | use syar\Server; 23 | use syar\log\File as FileLog; 24 | use syar\log\Log; 25 | 26 | $vendorPath = __Your vendor path__; 27 | /** @var \Composer\Autoload\ClassLoader $loader */ 28 | $loader = include($vendorPath . "/autoload.php"); 29 | $loader->addPsr4('syar\\example\\service\\', __DIR__ . '/service'); 30 | 31 | $server = new Server('0.0.0.0', '5604'); 32 | $server->setLogger(new Log()); 33 | $service = new \syar\example\service\Test(); 34 | $server->setDispatcher(function(\syar\Token $token, $isDocument) use ($service){ 35 | if(!$isDocument){ 36 | $method = $token->getMethod(); 37 | $params = $token->getArgs(); 38 | $value = call_user_func_array(array($service, $method), $params); 39 | } else { 40 | $value = "Yar api document"; 41 | } 42 | return $value; 43 | }); 44 | 45 | $server->run(['max_request' => 10000]); 46 | 47 | ``` 48 | 49 | example/service/Test.php 50 | 51 | ``` 52 | namespace syar\example\service; 53 | 54 | /** 55 | * Class Test 56 | * @package syar\example\service 57 | */ 58 | class Test { 59 | public function getName($userName){ 60 | return $userName . " Hello"; 61 | } 62 | 63 | public function getAge(){ 64 | return 20; 65 | } 66 | } 67 | 68 | ``` 69 | 70 | 命令行启动server.php 71 | 72 | ``` 73 | #php server.php 74 | 75 | ``` 76 | 77 | **客户端** 78 | ``` 79 | $url = "http://127.0.0.1:5604/test"; 80 | $client = new Yar_client($url); 81 | $name = $client->getName("tester"); 82 | $age = $client->getAge(); 83 | 84 | // 85 | echo "
\n";
 86 | var_dump($name);
 87 | var_dump($age);
 88 | 
 89 | ```
 90 | 
 91 | 
 92 | 
 93 | ## 简单性能测试(benchmark)
 94 | 测试脚本 example/benchmark/testSimple.php, 
 95 | 测试环境(虚拟机)
 96 | 
 97 | *   cpu: i5 - 4460
 98 | *   mem: 4G
 99 | *   os: centos6.5
100 | *   php: php7(fpm: 20进程, swoole: 18进程(8 worker + 10 task)
101 | 
102 | 
103 | 脚本一共完成44次接口调用:
104 | 
105 | 1.  简单接口调用 2次
106 | 1.  数据库查询接口调用2次
107 | 1.  并发简单接口调用 20次
108 | 1.  并发数据库查询接口调用 20次
109 | 
110 | ```
111 | function test($type, $times = 5, $limit = 5){
112 |     $timer = new Timer();
113 |     $benchmark = new Benchmark($type);
114 |     $rs[] = $benchmark->simpleTest(); // 2
115 |     $rs[] = $benchmark->dbTest($limit); // 2
116 |     $rs[] = $benchmark->batchTest($times, $limit); // 20
117 |     $rs[] = $benchmark->concurrentTest($times, $limit); // 20
118 |     $stop = $timer->stop();
119 | 
120 |     // 44 calls, 22 db, 22 normal
121 |     foreach($rs as $v){
122 |         var_dump($v);
123 |     }
124 |     
125 |     return $stop;
126 | }
127 | 
128 | // start test
129 | $times['syar'] = test('syar');
130 | $times['fpm'] = test('fpm');
131 | var_dump($times);
132 | 
133 | ---------------------------
134 | output: 
135 | 
136 | array(2) {
137 |   ["syar"]=>
138 |   float(0.01271)
139 |   ["fpm"]=>
140 |   float(0.08602)
141 | }
142 | 
143 | ```
144 | 在当前测试环境下,在使用syar批量接口请求,fpm环境下的执行时间大概是syar下的3 -- 6倍左右,
145 | 
146 | ### 简单压力测试
147 | 测试脚本 example/benchmark/testConcurrent.php, 50%接口随机查询数据库(10000条数据, 主要为测试接口通信性能)
148 | 
149 | *   syar 20个并发进程2.4w次接口调用, 用时2.6s秒左右, QPS 9300左右, 可能存在个别调用错误
150 | *   fpm 20个并发进程2.4w次接口调用, 用时15s秒左右, QPS 1600左右, 并产生大量Timeout was reached错误
151 | 
152 | 
153 | ## 扩展特性
154 | 
155 | ### 接口批量请求
156 | *   批量请求的接口,服务端使用多个任务进程并行执行
157 | *   请求地址 http://{your_server_address}/multiple
158 | *   调用方法名 function calls($requests);
159 |     $requests参数格式 [请求1数组, 请求2数组, ...], 
160 |     请求数据格式:['api' => ApiName, 'method' => MethodName, 'params' => []]
161 | *   单个接口执行错误, 服务端记录错误日志, 返回['code' => CODE, 'error' => ERROR MESSAGE]格式数组, 客户端自行处理
162 | 
163 | 客户端请求示例:
164 | ```
165 | #example/client_mul.php
166 | $vendorPath = ...;
167 | $loader = include($vendorPath . "/autoload.php");
168 | 
169 | $url = "http://127.0.0.1:5604/multiple";
170 | $client = new Yar_client($url);
171 | 
172 | $calls = [
173 | 	'age' => [
174 | 		'api' => '/test',
175 | 		'method' => 'getAge',
176 | 		'params' => []
177 | 	    ],
178 | 	'name' => [
179 | 		'api' => '/test',
180 | 		'method' => 'getName',
181 | 		'params' => [rand(1, 245301)]
182 | 	]
183 | ];
184 | $rs = $client->calls($calls);
185 | 
186 | var_dump($rs);
187 | ```
188 | 
189 | 
190 | ### Protocol插件与Dispatcher插件
191 | 
192 | 应用示例参考 example/server_plug.php, client_plug.php
193 | 
194 | Protocol触发事件:
195 | 
196 | 1.  Protocol::EVENT_REQUEST_BEFORE, 请求开始触发, 可以提前响应客户端, 中断正常解析流程
197 | 1.  Protocol::EVENT_RESPONSE_AFTER, 请求结束触发, 可以适用请求结束之后的处理工作,比如写日志等
198 | 
199 | Dispatcher触发事件:
200 | 
201 | 1.  Dispatcher::EVENT_REQUEST_BEFORE, Api接口执行前触发
202 | 1.  Dispatcher::EVENT_REQUEST_AFTER, Api接口执行后触发
203 | 
204 | 
205 | ### 投递任务到task进程异步执行
206 | 
207 | 应用示例参考 example/taskManagerServer.php
208 | 
209 | *   TaskMananger->regTask()
210 | *   TaskMananger->doTask()
211 | *   TaskMananger->doTasks()
212 | *   TaskMananger->doTasksAsync()
213 | 
214 | ## 已知问题
215 | 1.  未完成文档解析, 可使用自带的yar server显示文档
216 | 1.  由于代码是从私有框架独立出来,可能存在未知bug


--------------------------------------------------------------------------------
/src/TaskManager.php:
--------------------------------------------------------------------------------
  1 | server = $server;
 35 | 	    $this->server->on("task", array($this, 'onTask'));
 36 | 	    $this->server->on("finish", array($this, 'onFinish'));
 37 |     }
 38 | 
 39 |     /**
 40 |      * @param string $id
 41 |      * @param callback $taskCallback
 42 |      * @return $this
 43 |      */
 44 |     function regTask($id, $taskCallback) {
 45 |         $this->callbacks[$id] = $taskCallback;
 46 |         return $this;
 47 |     }
 48 | 
 49 |     /**
 50 |      * @param $id
 51 |      * @return bool
 52 |      */
 53 |     function has($id){
 54 |         return isset($this->callbacks[$id]);
 55 |     }
 56 | 
 57 | 	/**
 58 | 	 * 执行任务
 59 | 	 * @param $data
 60 | 	 * @return array|mixed
 61 | 	 */
 62 | 	private function processTask($data) {
 63 | 	    if(!is_array($data)
 64 | 		    || !isset($data['id'])
 65 | 		    || !($id = $data['id'])
 66 | 		    || !isset($this->callbacks[$id])
 67 | 	    ){
 68 | 		    return $this->getErrorInfo("Invalid task id");
 69 | 	    }
 70 | 
 71 | 	    try{
 72 | 		    return call_user_func_array($this->callbacks[$id], $data['params']);
 73 | 	    } catch(Exception $e){
 74 | 		    return $this->getErrorInfo($e);
 75 | 	    }
 76 |     }
 77 | 
 78 | 	/**
 79 | 	 * @param Exception|string $e
 80 | 	 * @return array
 81 | 	 */
 82 | 	private function getErrorInfo($e){
 83 | 		if(is_string($e)){
 84 | 			$e = new Exception($e);
 85 | 		}
 86 | 		return [
 87 | 			'error' => $e->getMessage(),
 88 | 			'code' => $e->getCode(),
 89 | 			'trace' => $e->getTraceAsString()
 90 | 		];
 91 | 	}
 92 | 
 93 | 	/**
 94 | 	 * @param $id
 95 | 	 * @param array $params
 96 | 	 * @param callback $finishCallback
 97 | 	 * @param array $bindArgs
 98 | 	 * @return void
 99 | 	 * @throws Exception
100 | 	 */
101 | 	function doTask($id, $params = [], $finishCallback = null, $bindArgs = []){
102 | 		if(!isset($this->callbacks[$id])){
103 | 			throw(new Exception("Invalid task id"));
104 | 		}
105 | 
106 |         $request = $this->getTaskArgs([$id, $params]);
107 | 		if($this->server->taskworker){
108 | 			// 在task进程, 任务同步执行
109 | 			$rs = $this->processTask($request);
110 | 			if($finishCallback){
111 | 				call_user_func($finishCallback, $rs, $bindArgs);
112 | 			}
113 | 		} else {
114 | 			$this->server->task($request, -1, function($serv, $task_id, $data) use($finishCallback, $bindArgs) {
115 | 				if($finishCallback){
116 | 					call_user_func($finishCallback, $data, $bindArgs);
117 | 				}
118 | 			});
119 | 		}
120 | 	}
121 | 
122 |     /**
123 |      * @param $requests [ [id, [params]], ...]
124 |      * @param $callback
125 |      * @param array $bindParams
126 |      * @param float $timeout
127 |      * @return mixed
128 |      */
129 |     public function doTasks($requests, $callback = null, $bindParams = [], $timeout = 10.0){
130 |         if($this->server->taskworker){
131 |             // 在task进程, 任务同步执行
132 | 	        $results = [];
133 |             foreach($requests as $index => $request){
134 | 	            $results[$index] = $this->processTask($this->getTaskArgs($request));
135 |             }
136 |         } else {
137 | 	        $tasks = [];
138 | 	        foreach($requests as $index => $request){
139 | 		        $tasks[$index] = $this->getTaskArgs($request);
140 | 	        }
141 | 	        $results = $this->server->taskWaitMulti($tasks, $timeout);
142 |         }
143 | 	    if($callback){
144 | 		    call_user_func($callback, $results, $bindParams);
145 | 	    }
146 |         return $results;
147 |     }
148 | 
149 |     private function getTaskArgs($request){
150 | 	    $params = isset($request[1])
151 | 		    ? (is_array($request[1]) ? $request[1] : array($request[1]))
152 | 		    : [];
153 | 	    return [
154 | 		    'id' => $request[0],
155 | 		    'params' => $params
156 | 	    ];
157 |     }
158 | 
159 | 	function onTask($serv, $task_id, $from_id, $data){
160 | 		return $this->processTask($data);
161 | 	}
162 | 
163 | 	/**
164 | 	 * @param $requests
165 | 	 * @param $callback
166 | 	 * @param array $bindParams
167 | 	 */
168 | 	public function doTasksAsync($requests, $callback, $bindParams = []){
169 | 		// init request status
170 | 		static::$mulIndex++;
171 | 		static::$mulRunMap[static::$mulIndex] = [
172 | 			'total' => count($requests),
173 | 			'finish' => 0,
174 | 			'start' => time(),
175 | 			'callback' => $callback,
176 | 			'bindParams' => $bindParams,
177 | 			'rs' => [],
178 | 		];
179 | 
180 | 		foreach($requests as $index => $request){
181 | 			// to task
182 | 			$taskId = $this->server->task($this->getTaskArgs($request));
183 | 			$this->runMap[$taskId] = [
184 | 				'isMul' => true,
185 | 				'order' => $index,
186 | 				'start' => time(),
187 | 				'mulIndex' => static::$mulIndex,
188 | 			];
189 | 		}
190 | 	}
191 | 
192 | 	/**
193 |      * @param $serv
194 |      * @param $task_id
195 |      * @param $data
196 |      */
197 |     function onFinish($serv, $task_id, $data){
198 |         if(!isset($this->runMap[$task_id])){
199 |             return;
200 |         }
201 | 
202 |         $taskInfo = $this->runMap[$task_id];
203 |         unset($this->runMap[$task_id]);
204 |         if($taskInfo['isMul']){
205 |             $status =& self::$mulRunMap[$taskInfo['mulIndex']];
206 |             $status['finish']++;
207 | 
208 |             $rsOrder = $taskInfo['order'];
209 |             $status['rs'][$rsOrder] = $data;
210 |             //$isExpire = (time() - $taskInfo['start']) > $this->maxRunTimes;
211 |             if($status['finish'] < $status['total']){
212 |                 return;
213 |             }
214 | 
215 |             call_user_func(
216 |                 $status['callback'],
217 |                 $status['rs'],
218 |                 $status['bindParams']
219 |             );
220 | 
221 |             unset($status);
222 |             unset(self::$mulRunMap[$taskInfo['mulIndex']]);
223 |         } else {
224 |             if(isset($taskInfo['callback'])){
225 |                 call_user_func($taskInfo['callback'], $data, $taskInfo['bindParams']);
226 |             }
227 |         }
228 |     }
229 | }


--------------------------------------------------------------------------------
/src/Server.php:
--------------------------------------------------------------------------------
  1 |  10240,
 42 | 		'worker_num' => 8,       //worker process num
 43 | 		'max_request' => 10000,
 44 | 		'task_worker_num' => 10,
 45 | 		'task_max_request' => 10000,
 46 | 		'backlog' => 128,        //listen backlog
 47 | 		'open_tcp_keepalive' => 1,
 48 | 		'heartbeat_check_interval' => 5,
 49 | 		'heartbeat_idle_time' => 10,
 50 | 		'http_parse_post' => false,
 51 | 	);
 52 | 
 53 |     function __construct($host, $port) {
 54 |         $this->host = $host;
 55 |         $this->port = $port;
 56 | 	    $this->sw = $this->createServer();
 57 |     }
 58 | 
 59 | 	protected function createServer(){
 60 | 		return new swoole_http_server($this->host, $this->port);
 61 | 	}
 62 | 
 63 | 	/**
 64 | 	 * @param $key
 65 | 	 * @param null $value
 66 | 	 * @return $this
 67 | 	 */
 68 | 	function setOption($key, $value = null){
 69 |         if(is_array($key)){
 70 | 	        $this->setting = array_merge($this->setting, $key);
 71 |         } else {
 72 | 	        $this->setting[$key] = $value;
 73 |         }
 74 | 		return $this;
 75 |     }
 76 | 
 77 | 
 78 |     function daemonize() {
 79 |         $this->setting['daemonize'] = 1;
 80 |     }
 81 | 
 82 |     /**
 83 |      * @param $protocol
 84 |      * @return $this
 85 |      * @throws \Exception
 86 |      */
 87 |     function setProtocol($protocol){
 88 | 	    $this->protocol = $protocol;
 89 | 	    return $this;
 90 |     }
 91 | 
 92 |     /**
 93 |      * @return Protocol
 94 |      * @throws \Exception
 95 |      */
 96 |     public function getProtocol() {
 97 | 	    if(!isset($this->protocol)){
 98 | 		    $this->protocol = new Protocol();
 99 | 	    }
100 | 	    return $this->protocol;
101 |     }
102 | 
103 | 	/**
104 | 	 * @return TaskManager
105 | 	 */
106 | 	public function getTaskManager(){
107 | 		if(!isset($this->taskManager)){
108 | 			$this->taskManager = new TaskManager($this->sw);
109 | 		}
110 | 		return $this->taskManager;
111 | 	}
112 | 
113 | 	/**
114 | 	 * @return swoole_http_server|swoole_server
115 | 	 */
116 | 	function getSwooleServer(){
117 | 		return $this->sw;
118 | 	}
119 | 
120 |     /**
121 |      * @param array $setting
122 |      */
123 |     function run($setting = array()) {
124 |         register_shutdown_function(array($this, 'handleFatal'));
125 | 
126 |         // set options
127 |         $this->setOption($setting);
128 |         $this->sw->set($this->setting);
129 | 
130 |         // bind method for swoole server
131 | 	    $this->chkConfig();
132 |         $this->bind();
133 | 
134 |         // start server
135 |         $this->sw->start();
136 |     }
137 | 
138 |     protected function chkConfig(){
139 | 	    $protocol = $this->getProtocol();
140 |         $protocol->server = $this;
141 |         $protocol->chkConfig();
142 |     }
143 | 
144 |     protected function bind(){
145 |         $binds = [
146 |             'onServerStart' => 'ManagerStart',
147 |             'onServerStop' => 'ManagerStop',
148 |             ];
149 |         foreach($binds as $method => $evt){
150 |             $this->sw->on($evt, array($this, $method));
151 |         }
152 | 
153 |         $protocol = $this->getProtocol();
154 |         $binds = [
155 |             'onServerStart' => 'ManagerStart',
156 |             'onServerStop' => 'ManagerStop',
157 | 
158 |             'onWorkerStart' => 'WorkerStart',
159 |             'onWorkerStop' => 'WorkerStop',
160 | 
161 |             'onConnect' => 'Connect',
162 |             'onReceive' => 'Receive',
163 |             'onClose' => 'Close',
164 |             'onRequest' => 'request',
165 |             ];
166 |         foreach($binds as $method => $evt){
167 |             if(method_exists($protocol, $method)){
168 |                 $this->sw->on($evt, array($protocol, $method));
169 |             }
170 |         }
171 |     }
172 | 
173 |     function onServerStart($serv){
174 |         $this->log("Server start on {$this->host}:{$this->port}, pid {$serv->master_pid}");
175 |         if (!empty($this->setting['pid_file'])){
176 |             file_put_contents($this->setting['pid_file'], $serv->master_pid);
177 |         }
178 |     }
179 | 
180 |     function onServerStop(){
181 |         $this->log("Server stop");
182 |         if (!empty($this->setting['pid_file'])) {
183 |             unlink($this->setting['pid_file']);
184 |         }
185 |     }
186 | 
187 | 
188 |     private $currentRequest = [];
189 | 
190 |     /**
191 |      * @param $req
192 |      * @param Response $response
193 |      */
194 |     public function setCurrentRequest($req, $response){
195 |         $this->currentRequest = [
196 |             'request' => $req,
197 |             'response' => $response,
198 |         ];
199 |     }
200 | 
201 | 	/**
202 | 	 * catch error
203 | 	 */
204 | 	function handleFatal(){
205 | 		if($log = Debug::traceError()) {
206 | 			$this->log($log, "error");
207 | 		}
208 | 
209 | 		if(isset($this->currentRequest['response'])){
210 |             $this->currentRequest['response']->end("PHP Parse error:" . $log);
211 |         }
212 | 	}
213 | 
214 | 	/**
215 | 	 * @param $callback
216 | 	 */
217 | 	function setDispatcher($callback){
218 | 		$this->getProtocol()->setProcess($callback);
219 | 	}
220 | 
221 | 	/**
222 | 	 * @param $plug
223 | 	 * @param $forProtocol
224 | 	 * @return $this
225 | 	 */
226 | 	public function addPlug($plug, $forProtocol = true){
227 | 		if($forProtocol){
228 |             $dispatcher = $this->getProtocol();
229 | 		} else {
230 |             $dispatcher = $this->getProtocol()->getProcessor();
231 | 		}
232 | 
233 |         if($dispatcher instanceof InterfaceEventDispatcher){
234 |             $dispatcher->attaches($plug);
235 |         }
236 | 		return $this;
237 | 	}
238 | }


--------------------------------------------------------------------------------
/example/benckmark/lib.php:
--------------------------------------------------------------------------------
  1 | simpleTest(); // 2
 20 |     $rs[] = $benchmark->dbTest(); // 2
 21 | 
 22 |     $times = $times ?: ($batch ? 5 : 1);
 23 |     if($batch){
 24 |         $rs[] = $benchmark->batchTest($times); // 4 * $times
 25 |     } else {
 26 |         $rs[] = $benchmark->concurrentTest($times); // 4 * $times
 27 |     }
 28 | 
 29 |     $stop = $timer->stop();
 30 | 
 31 |     // 44 calls, 22 db, 22 normal
 32 |     if(IS_OUTPUT){
 33 |         echo "{$type} -----------------------------\n";
 34 |         foreach($rs as $v){
 35 |             var_dump($v);
 36 |         }
 37 |     }
 38 | 
 39 |     return $stop;
 40 | }
 41 | 
 42 | function ab($type = 'syar', $c = 1, $n = 1, $batch = false, $times = 1) {
 43 |     $pm = new SimpleProcessorManager();
 44 |     $timer = new Timer();
 45 | 
 46 |     $pm->run($c, function(swoole_process $worker) use($type, $n, $batch, $times){
 47 |         for($i = 0; $i < $n; $i++){
 48 |             echo "Worker {$worker->pid} for the {$i}th\n";
 49 |             test($type, $batch, $times);
 50 |         }
 51 |         $worker->exit(0);
 52 |     });
 53 |     return $timer->stop();
 54 | }
 55 | 
 56 | /**
 57 |  * Class Benchmark
 58 |  * @package syar\example\benckmark
 59 |  */
 60 | class Benchmark {
 61 | 
 62 |     protected $type = 'syar';
 63 |     protected $url;
 64 |     protected $urlFpm = "http://syar7.x1.cn/fpm_yar_%s.php";
 65 |     protected $urlSyar = "http://127.0.0.1:5604/%s";
 66 | 
 67 |     /**
 68 |      * Benchmark constructor.
 69 |      * @param string $type
 70 |      */
 71 |     public function __construct($type){
 72 |         $this->type = $type;
 73 |         $this->url = $this->isFpm() ? $this->urlFpm : $this->urlSyar;
 74 |     }
 75 | 
 76 |     private function isFpm(){
 77 |         return $this->type == 'fpm';
 78 |     }
 79 | 
 80 |     /**
 81 |      * 1万表记录测试数据
 82 |      * @var int
 83 |      * @see file : mkTestData.php
 84 |      */
 85 |     private $maxTableId = 10000;
 86 |     // 列表查询取前2w记录随机
 87 |     private $maxTableStart = 5000;
 88 | 
 89 |     /**
 90 |      * @return int
 91 |      */
 92 |     private function getInfoId(){
 93 |         return rand(1, $this->maxTableId);
 94 |     }
 95 | 
 96 |     /**
 97 |      * @param int $limit
 98 |      * @return int
 99 |      */
100 |     private function getStart($limit = 5){
101 |         $start = rand($limit, $this->maxTableStart) - $limit;
102 |         if($start < 0){
103 |             $start = 0;
104 |         }
105 |         return $start;
106 |     }
107 | 
108 |     /**
109 |      * @param $api
110 |      * @param bool $returnUrl
111 |      * @return string|Yar_Client
112 |      */
113 |     function getClient($api, $returnUrl = false){
114 |         $url = sprintf($this->url, $api);
115 |         if($returnUrl){
116 |             return $url;
117 |         }
118 | 
119 |         $client = new Yar_Client($url);
120 |         $client->setOpt(YAR_OPT_PACKAGER, 'msgpack');
121 |         return $client;
122 |     }
123 | 
124 |     function dbTest(){
125 |         $client = $this->getClient('db');
126 |         $info = $client->getInfo($this->getInfoId());
127 |         $list = $client->getList($this->getStart(), $this->listLimit);
128 |         return [$info, $list];
129 |     }
130 | 
131 |     function simpleTest(){
132 |         $client = $this->getClient('test');
133 |         $name = $client->getName("tester");
134 |         $age = $client->getAge();
135 |         return [$name, $age];
136 |     }
137 | 
138 |     /**
139 |      * @param int $times
140 |      * @return array
141 |      */
142 |     function concurrentTest($times = 1){
143 |         $data = [];
144 | 
145 |         $url1 = $this->getClient('test', true);
146 |         $url2 = $this->getClient('db', true);
147 |         for($i = 0; $i < $times; $i++){
148 |             $this->concurrentCall($data, $url1, 'getName', [rand(0, 245301)], 'name_' . $i);
149 |             $this->concurrentCall($data, $url1, 'getAge', [], 'age_' . $i);
150 |             $this->concurrentCall($data, $url2, 'getInfo', [$this->getInfoId()], 'info_' . $i);
151 |             $this->concurrentCall($data, $url2, 'getList', [$this->getStart(), $this->listLimit], 'list_' . $i);
152 |         }
153 |         Yar_Concurrent_Client::loop();
154 |         Yar_Concurrent_Client::reset();
155 |         return $data;
156 |     }
157 | 
158 |     private function concurrentCall(&$data, $url, $method, $args, $key){
159 |         Yar_Concurrent_Client::call(
160 |             $url, $method, $args,
161 |             function($rs) use ($key, &$data){
162 |                 $data[$key] = $rs;
163 |             }
164 |         );
165 |     }
166 | 
167 |     protected $listLimit = 2;
168 | 
169 |     /**
170 |      * @param int $times
171 |      * @return array
172 |      */
173 |     function batchTest($times = 1) {
174 |         if($this->isFpm()){
175 |             return $this->concurrentTest($times);
176 |         }
177 |         $requests = [];
178 |         for($i = 0; $i < $times; $i++){
179 |             $requests["age_{$i}"] = ['api' => 'test', 'method' => 'getAge', 'params' => []];
180 |             $requests["name_{$i}"] = ['api' => 'test', 'method' => 'getName', 'params' => ['test']];
181 |             $requests["info_{$i}"] = ['api' => 'db', 'method' => 'getInfo', 'params' => [$this->getInfoId()]];
182 |             $requests["list_{$i}"] = ['api' => 'db', 'method' => 'getList', 'params' => [$this->getStart(), $this->listLimit]];
183 |         }
184 |         $client = $this->getClient('multiple');
185 |         return $client->calls($requests);
186 |     }
187 | }
188 | 
189 | /**
190 |  * Class Timer
191 |  */
192 | class Timer{
193 |     protected $startTime;
194 | 
195 |     function __construct($autoStart = true){
196 |         if($autoStart){
197 |             $this->start();
198 |         }
199 |     }
200 | 
201 |     function start(){
202 |         $this->startTime = $this->_time();
203 |     }
204 | 
205 |     function stop($echo = false, $str = ''){
206 |         $time = $this->_time();
207 |         $times = round($time - $this->startTime, 5);
208 |         $this->startTime = $time;
209 | 
210 |         if($echo){
211 |             echo $str . $times . "\n";
212 |         }
213 |         return $times;
214 |     }
215 | 
216 |     protected function _time(){
217 |         $mtime = microtime ();
218 |         $mtime = explode (' ', $mtime);
219 |         return $mtime[1] + $mtime[0];
220 |     }
221 | }
222 | 
223 | 
224 | /**
225 |  * Class SimpleProcessorManager
226 |  * @package j\debug
227 |  */
228 | class SimpleProcessorManager{
229 |     protected $works = [];
230 |     protected $workNums;
231 |     protected function start($callback){
232 |         for($i = 0; $i < $this->workNums; $i++) {
233 |             $process = new swoole_process($callback, false, false);
234 |             $pid = $process->start();
235 |             $workers[$pid] = $process;
236 |         }
237 |     }
238 | 
239 |     function run($workNums, $callback){
240 |         $this->workNums = $workNums;
241 |         $this->start($callback);
242 |         $this->close();
243 |     }
244 | 
245 |     protected function close(){
246 |         for($i = 0; $i < $this->workNums; $i++) {
247 |             $ret = swoole_process::wait();
248 |             $pid = $ret['pid'];
249 |             echo "Worker Exit, PID=" . $pid . PHP_EOL;
250 |         }
251 |     }
252 | }
253 | 
254 | 


--------------------------------------------------------------------------------
/src/Protocol.php:
--------------------------------------------------------------------------------
  1 | packer = new Packer();
 46 |     }
 47 | 
 48 |     /**
 49 |      * @throws RuntimeException
 50 |      */
 51 |     public function chkConfig(){
 52 |         if(!isset($this->server)){
 53 |             throw new RuntimeException("Set protocol's server first");
 54 |         }
 55 | 
 56 |         if(!is_callable($this->getProcessor())){
 57 |             throw new RuntimeException("Set protocol's processor first");
 58 |         }
 59 | 
 60 |         $taskManager = $this->server->getTaskManager();
 61 |         $taskManager->regTask('process', array($this, 'process'));
 62 |     }
 63 | 
 64 |     /**
 65 |      * @param $callback
 66 |      */
 67 |     function setProcess($callback){
 68 |         $this->processor = $callback;
 69 |     }
 70 | 
 71 |     /**
 72 |      * @return callable|Dispatcher
 73 |      */
 74 |     public function getProcessor(){
 75 |         if(!isset($this->processor)){
 76 |             $this->processor = new Dispatcher();
 77 |         }
 78 |         return $this->processor;
 79 |     }
 80 | 
 81 |     static $i = 1;
 82 |     /**
 83 |      * @param swoole_http_request $req
 84 |      * @param swoole_http_response $res
 85 |      */
 86 |     function onRequest(swoole_http_request $req, swoole_http_response $res) {
 87 |         $request = new Request($req);
 88 |         $response = new Response($res);
 89 |         $this->server->setCurrentRequest($request, $response);
 90 | 
 91 |         if($request->isPost()) {
 92 |             $request->yar = $this->packer->unpack($req->rawContent());
 93 |         }
 94 | 
 95 |         if($this->hasListener(self::EVENT_REQUEST_BEFORE)){
 96 |             $this->trigger(self::EVENT_REQUEST_BEFORE, $request, $response, $this);
 97 |             if($response->isSend()){
 98 |                 return;
 99 |             }
100 |         }
101 | 
102 |         if(isset($request->yar)) {
103 |             if($request->yar->isError()){
104 |                 // 解包错误
105 |                 $this->response([], $request, $response);
106 |                 return;
107 |             }
108 | 
109 |             if($this->isMulApi($request)){
110 |                 // 批量请求
111 |                 $this->mulRequest($request, $response);
112 |                 return;
113 |             }
114 | 
115 |             $isDocument = false;
116 |             $method = $request->getYarMethod();
117 |             $params = $request->getYarParams();
118 |         } else {
119 |             $isDocument = true;
120 |             $method = $params = '';
121 |         }
122 | 
123 |         // process request
124 |         $get = isset($req->get) ? $req->get : [];
125 |         $token = new Token($request->getPath(),  $method,  $params, $get);
126 | 
127 |         $rs = $this->process($token, $isDocument);
128 |         $this->response($rs, $request, $response);
129 |     }
130 | 
131 | 	/**
132 | 	 * @param $request Request
133 | 	 * @return bool
134 | 	 */
135 | 	protected function isMulApi($request) {
136 | 		return
137 | 			$this->multipleApiPath == $request->getPath() &&
138 | 			$this->multipleApiMethod == $request->yar->getRequestMethod();
139 | 	}
140 | 
141 |     /**
142 |      * @param $request Request
143 |      * @param $response Response
144 |      */
145 |     protected function mulRequest($request, $response){
146 |         $params = $request->yar->getRequestParams();
147 | 
148 | 	    // maybe a bug
149 | 	    // $client->calls($calls);
150 | 	    // $params = $calls
151 |         $params = $params[0];
152 | 
153 |         if(!is_array($params) || count($params) == 0){
154 |             $this->response([
155 |                 'code' => 500,
156 |                 "error" => "Invalid request params for multiple request"
157 |             ], $request, $response);
158 |             return;
159 |         }
160 | 
161 | 	    $requests = [];
162 |         foreach($params as $key => $param){
163 | 	        $token = new Token(
164 | 		        $param['api'], $param['method'], $param['params'],
165 | 		        isset($param['options']) ? $param['options'] : []
166 | 	            );
167 | 	        $requests[$key] = ['process', [$token]] ;
168 |         }
169 | 
170 |         // init
171 |         $this->server->getTaskManager()->doTasksAsync($requests, function($results) use($request, $response){
172 | 	        $this->response(['code' => 200, 'rs' => $this->formatMulResults($results)], $request, $response);
173 |         });
174 | 
175 | //        $results = $this->server->getTaskManager()->doTasks($requests);
176 | //        $this->response(['code' => 200, 'rs' => $this->formatMulResults($results)], $request, $response);
177 |     }
178 | 
179 |     protected function formatMulResults($results){
180 |         $data = [];
181 |         foreach($results as $key => $rs) {
182 |             if($rs['code'] == 200){
183 |                 $data[$key] = $rs['rs'];
184 |             } else {
185 |                 unset($rs['debug']);
186 |                 $data[$key] = $rs;
187 |             }
188 |         }
189 |         return $data;
190 |     }
191 | 
192 |     /**
193 |      * @param $requestToken
194 |      * @param $isDocument
195 |      * @return array
196 |      */
197 |     public function process($requestToken, $isDocument = false) {
198 |         try{
199 |             return  [
200 |                 'code' => 200,
201 |                 'rs' => call_user_func($this->processor, $requestToken, $isDocument, $this)
202 |             ];
203 |         } catch (\Exception $e) {
204 | 	        $error = [
205 | 		        'code' => $e->getCode(),
206 | 		        'error' => $e->getMessage(),
207 | 		        'debug' => $e->getTraceAsString()
208 | 	        ];
209 | 	        $this->log(var_export($error), 'error');
210 | 	        return $error;
211 |         }
212 |     }
213 | 
214 |     /**
215 |      * @param string $data
216 |      * @param Request $request
217 |      * @param Response $response
218 |      */
219 |     protected function response($data, $request, $response){
220 |         if($response->isSend()){
221 |             return;
222 |         }
223 | 
224 |         if($yar = $request->yar){
225 |             if(!$yar->isError()){
226 |                 if($data['code'] == 200){
227 |                     $yar->setReturnValue($data['rs']);
228 |                 } else {
229 |                     $yar->setError($data['error']);
230 |                 }
231 |             }
232 |             $response->setHeader('Content-Type', 'application/octet-stream');
233 |             $response->body = $this->packer->pack($request->yar);
234 |         } else {
235 |             $response->setHttpStatus(500);
236 |             $response->body = $data['code'] == 200 ? $data['rs'] : $data['error'];
237 |         }
238 | 
239 |         //压缩
240 |         if ($this->gzip) {
241 |             $response->gzip($this->gzip_level);
242 |         }
243 | 
244 |         // 输出返回
245 |         $response->send();
246 | 
247 |         if($this->hasListener(self::EVENT_RESPONSE_AFTER)){
248 |             $this->trigger(self::EVENT_RESPONSE_AFTER, $request, $response, $data, $this);
249 |         }
250 |     }
251 | }


--------------------------------------------------------------------------------