StartCommand.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Command;
  4. use Swoole\Process;
  5. use Symfony\Component\Console\Command\Command;
  6. use Hyperf\Contract\ConfigInterface;
  7. use Hyperf\Contract\StdoutLoggerInterface;
  8. use Hyperf\Server\ServerFactory;
  9. use InvalidArgumentException;
  10. use Psr\Container\ContainerInterface;
  11. use Psr\EventDispatcher\EventDispatcherInterface;
  12. use Swoole\Runtime;
  13. use Symfony\Component\Console\Input\InputInterface;
  14. use Symfony\Component\Console\Input\InputOption;
  15. use Symfony\Component\Console\Output\OutputInterface;
  16. use Symfony\Component\Console\Style\SymfonyStyle;
  17. use Hyperf\Command\Annotation\Command as HyperfCommand;
  18. //php bin/hyperf.php start -d //启动服务并进入后台模式
  19. //php bin/hyperf.php start -c //启动服务并清除 runtime/container 目录
  20. //php bin/hyperf.php start -w //启动服务并监控 app、config目录以及 .env 变化自动重启
  21. //php bin/hyperf.php start -w -p /bin/php //启动 watch 服务,参数 p 指定 php 安装目录
  22. //php bin/hyperf.php start -w -t 10 //启动 watch 服务,参数 t 指定 watch 时间间隔,单位秒
  23. //php bin/hyperf.php stop //停止服务
  24. //php bin/hyperf.php restart //重启服务
  25. //php bin/hyperf.php restart -c //重启服务并清除 runtime/container 目录
  26. /**
  27. * @HyperfCommand()
  28. */
  29. class StartCommand extends Command
  30. {
  31. /**
  32. * @var ContainerInterface
  33. */
  34. private $container;
  35. /**
  36. * @var SymfonyStyle
  37. */
  38. private $io;
  39. /**
  40. * @var int
  41. */
  42. private $interval;
  43. /**
  44. * @var bool
  45. */
  46. private $clear;
  47. /**
  48. * @var bool
  49. */
  50. private $daemonize;
  51. /**
  52. * @var string
  53. */
  54. private $php;
  55. public function __construct(ContainerInterface $container)
  56. {
  57. $this->container = $container;
  58. parent::__construct('start');
  59. }
  60. protected function configure()
  61. {
  62. $this
  63. ->setDescription('Start hyperf servers.')
  64. ->addOption('daemonize', 'd', InputOption::VALUE_OPTIONAL, 'swoole server daemonize', false)
  65. ->addOption('clear', 'c', InputOption::VALUE_OPTIONAL, 'clear runtime container', false)
  66. ->addOption('watch', 'w', InputOption::VALUE_OPTIONAL, 'watch swoole server', false)
  67. ->addOption('interval', 't', InputOption::VALUE_OPTIONAL, 'interval time ( 1-15 seconds)', 3)
  68. ->addOption('php', 'p', InputOption::VALUE_OPTIONAL, 'which php');
  69. }
  70. protected function execute(InputInterface $input, OutputInterface $output)
  71. {
  72. $this->io = new SymfonyStyle($input, $output);
  73. $this->checkEnvironment($output);
  74. $this->stopServer();
  75. $this->clear = ($input->getOption('clear') !== false);
  76. $this->daemonize = ($input->getOption('daemonize') !== false);
  77. if ($input->getOption('watch') !== false) {
  78. $this->interval = (int)$input->getOption('interval');
  79. if ($this->interval < 0 || $this->interval > 15) {
  80. $this->interval = 3;
  81. }
  82. if (!$this->php = $input->getOption('php')) {
  83. if (!$this->php = exec('which php')) {
  84. $this->php = 'php';
  85. }
  86. }
  87. $this->watchServer();
  88. } else {
  89. if ($this->clear) {
  90. $this->clearRuntimeContainer();
  91. }
  92. $this->startServer();
  93. }
  94. return 0;
  95. }
  96. private function checkEnvironment(OutputInterface $output)
  97. {
  98. /**
  99. * swoole.use_shortname = true => string(1) "1" => enabled
  100. * swoole.use_shortname = "true" => string(1) "1" => enabled
  101. * swoole.use_shortname = on => string(1) "1" => enabled
  102. * swoole.use_shortname = On => string(1) "1" => enabled
  103. * swoole.use_shortname = "On" => string(2) "On" => enabled
  104. * swoole.use_shortname = "on" => string(2) "on" => enabled
  105. * swoole.use_shortname = 1 => string(1) "1" => enabled
  106. * swoole.use_shortname = "1" => string(1) "1" => enabled
  107. * swoole.use_shortname = 2 => string(1) "1" => enabled
  108. * swoole.use_shortname = false => string(0) "" => disabled
  109. * swoole.use_shortname = "false" => string(5) "false" => disabled
  110. * swoole.use_shortname = off => string(0) "" => disabled
  111. * swoole.use_shortname = Off => string(0) "" => disabled
  112. * swoole.use_shortname = "off" => string(3) "off" => disabled
  113. * swoole.use_shortname = "Off" => string(3) "Off" => disabled
  114. * swoole.use_shortname = 0 => string(1) "0" => disabled
  115. * swoole.use_shortname = "0" => string(1) "0" => disabled
  116. * swoole.use_shortname = 00 => string(2) "00" => disabled
  117. * swoole.use_shortname = "00" => string(2) "00" => disabled
  118. * swoole.use_shortname = "" => string(0) "" => disabled
  119. * swoole.use_shortname = " " => string(1) " " => disabled.
  120. */
  121. $useShortname = ini_get_all('swoole')['swoole.use_shortname']['local_value'];
  122. $useShortname = strtolower(trim(str_replace('0', '', $useShortname)));
  123. if (!in_array($useShortname, ['', 'off', 'false'], true)) {
  124. $output->writeln('<error>ERROR</error> Swoole short name have to disable before start server, please set swoole.use_shortname = off into your php.ini.');
  125. exit(0);
  126. }
  127. }
  128. private function clearRuntimeContainer()
  129. {
  130. exec('rm -rf ' . BASE_PATH . '/runtime/container');
  131. }
  132. private function startServer()
  133. {
  134. $serverFactory = $this->container->get(ServerFactory::class)
  135. ->setEventDispatcher($this->container->get(EventDispatcherInterface::class))
  136. ->setLogger($this->container->get(StdoutLoggerInterface::class));
  137. $serverConfig = $this->container->get(ConfigInterface::class)->get('server', []);
  138. if (!$serverConfig) {
  139. throw new InvalidArgumentException('At least one server should be defined.');
  140. }
  141. if ($this->daemonize) {
  142. $serverConfig['settings']['daemonize'] = 1;
  143. $this->io->success('swoole server start success.');
  144. }
  145. Runtime::enableCoroutine(true, swoole_hook_flags());
  146. $serverFactory->configure($serverConfig);
  147. $serverFactory->start();
  148. }
  149. private function stopServer()
  150. {
  151. $pidFile = BASE_PATH . '/runtime/hyperf.pid';
  152. $pid = file_exists($pidFile) ? intval(file_get_contents($pidFile)) : false;
  153. if ($pid && Process::kill($pid, SIG_DFL)) {
  154. if (!Process::kill($pid, SIGTERM)) {
  155. $this->io->error('old swoole server stop error.');
  156. die();
  157. }
  158. while (Process::kill($pid, SIG_DFL)) {
  159. sleep(1);
  160. }
  161. }
  162. }
  163. private function watchServer()
  164. {
  165. $this->io->note('start new swoole server ...');
  166. $pid = $this->startProcess();
  167. while ($pid > 0) {
  168. $this->watch();
  169. $this->io->note('restart swoole server ...');
  170. $this->stopProcess($pid);
  171. $pid = $this->startProcess();
  172. sleep(1);
  173. }
  174. }
  175. private function startProcess()
  176. {
  177. $this->clearRuntimeContainer();
  178. $process = new Process(function (Process $process) {
  179. $args = [BASE_PATH . '/bin/hyperf.php', 'start'];
  180. if ($this->daemonize) {
  181. $args[] = '-d';
  182. }
  183. $process->exec($this->php, $args);
  184. });
  185. return $process->start();
  186. }
  187. private function stopProcess(int $pid): bool
  188. {
  189. $this->io->text('stop old swoole server. pid:' . $pid);
  190. $timeout = 15;
  191. $startTime = time();
  192. while (true) {
  193. if ($ret = Process::wait(false) && $ret['pid'] == $pid) {
  194. return true;
  195. }
  196. if (!Process::kill($pid, SIG_DFL)) {
  197. return true;
  198. }
  199. if ((time() - $startTime) >= $timeout) {
  200. $this->io->error('stop old swoole server timeout.');
  201. return false;
  202. }
  203. Process::kill($pid, SIGTERM);
  204. sleep(1);
  205. }
  206. return false;
  207. }
  208. private function monitorDirs(bool $recursive = false)
  209. {
  210. $dirs[] = BASE_PATH . '/app';
  211. $dirs[] = BASE_PATH . '/config';
  212. if ($recursive) {
  213. foreach ($dirs as $dir) {
  214. $dirIterator = new \RecursiveDirectoryIterator($dir);
  215. $iterator = new \RecursiveIteratorIterator($dirIterator, \RecursiveIteratorIterator::SELF_FIRST);
  216. /** @var \SplFileInfo $file */
  217. foreach ($iterator as $file) {
  218. if ($file->isDir() && $file->getFilename() != '.' && $file->getFilename() != '..') {
  219. $dirs[] = $file->getPathname();
  220. }
  221. }
  222. }
  223. }
  224. return $dirs;
  225. }
  226. private function monitorFiles()
  227. {
  228. $files[] = BASE_PATH . '/.env';
  229. return $files;
  230. }
  231. private function watch()
  232. {
  233. if (extension_loaded('inotify')) {
  234. return $this->inotifyWatch();
  235. } else {
  236. return $this->fileWatch();
  237. }
  238. }
  239. private function inotifyWatch()
  240. {
  241. $fd = inotify_init();
  242. stream_set_blocking($fd, 0);
  243. $dirs = $this->monitorDirs(true);
  244. foreach ($dirs as $dir) {
  245. inotify_add_watch($dir, IN_CLOSE_WRITE | IN_CREATE | IN_DELETE);
  246. }
  247. $files = $this->monitorFiles();
  248. foreach ($files as $file) {
  249. inotify_add_watch($file, IN_CLOSE_WRITE | IN_CREATE | IN_DELETE);
  250. }
  251. while (true) {
  252. sleep($this->interval);
  253. if (inotify_read($fd)) {
  254. break;
  255. }
  256. }
  257. fclose($fd);
  258. }
  259. private function fileWatch()
  260. {
  261. $dirs = $this->monitorDirs();
  262. $files = $this->monitorFiles();
  263. $inodeListOld = [];
  264. $inodeListNew = [];
  265. $isFrist = true;
  266. while (true) {
  267. foreach ($dirs as $dir) {
  268. $dirIterator = new \RecursiveDirectoryIterator($dir);
  269. $iterator = new \RecursiveIteratorIterator($dirIterator, \RecursiveIteratorIterator::LEAVES_ONLY);
  270. /** @var \SplFileInfo $file */
  271. foreach ($iterator as $file) {
  272. if ($file->isFile() && in_array(strtolower($file->getExtension()), ['env', 'php'])) {
  273. $inode = $file->getInode();
  274. $sign = $file->getFilename() . $file->getMTime();
  275. if ($isFrist) {
  276. $inodeListOld[$inode] = $sign;
  277. } else {
  278. // add new file || file changed
  279. if (!isset($inodeListOld[$inode]) || $inodeListOld[$inode] != $sign) {
  280. return true;
  281. } else {
  282. $inodeListNew[$inode] = $sign;
  283. }
  284. }
  285. }
  286. }
  287. }
  288. foreach ($files as $key => $file) {
  289. if (file_exists($file)) {
  290. $file = new \SplFileInfo($file);
  291. $inode = $file->getInode();
  292. $sign = $file->getFilename() . $file->getMTime();
  293. if ($isFrist) {
  294. $inodeListOld[$inode] = $sign;
  295. } else {
  296. // add new file || file changed
  297. if (!isset($inodeListOld[$inode]) || $inodeListOld[$inode] != $sign) {
  298. return true;
  299. } else {
  300. $inodeListNew[$inode] = $sign;
  301. }
  302. }
  303. }
  304. }
  305. if ($isFrist) {
  306. $isFrist = false;
  307. } else {
  308. // file remove
  309. if (!empty(array_diff($inodeListOld, $inodeListNew))) {
  310. return true;
  311. }
  312. }
  313. sleep($this->interval);
  314. }
  315. }
  316. }