Worker.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  1. <?php
  2. namespace Workerman;
  3. use \Workerman\Events\Libevent;
  4. use \Workerman\Events\Select;
  5. use \Workerman\Events\EventInterface;
  6. use \Workerman\Connection\ConnectionInterface;
  7. use \Workerman\Connection\TcpConnection;
  8. use \Workerman\Connection\UdpConnection;
  9. use \Workerman\Lib\Timer;
  10. use \Exception;
  11. /**
  12. *
  13. * @author walkor<walkor@workerman.net>
  14. */
  15. class Worker
  16. {
  17. /**
  18. * workerman version
  19. * @var string
  20. */
  21. const VERSION = '3.0.0';
  22. /**
  23. * status starting
  24. * @var int
  25. */
  26. const STATUS_STARTING = 1;
  27. /**
  28. * status running
  29. * @var int
  30. */
  31. const STATUS_RUNNING = 2;
  32. /**
  33. * status shutdown
  34. * @var int
  35. */
  36. const STATUS_SHUTDOWN = 4;
  37. /**
  38. * status reloading
  39. * @var int
  40. */
  41. const STATUS_RELOADING = 8;
  42. /**
  43. * after KILL_WORKER_TIMER_TIME seconds if worker not quit
  44. * then send SIGKILL to the worker
  45. * @var int
  46. */
  47. const KILL_WORKER_TIMER_TIME = 1;
  48. /**
  49. * backlog
  50. * @var int
  51. */
  52. const DEFAUL_BACKLOG = 1024;
  53. /**
  54. * max udp package size
  55. * @var int
  56. */
  57. const MAX_UDP_PACKEG_SIZE = 65535;
  58. /**
  59. * worker name for marking process
  60. * @var string
  61. */
  62. public $name = 'none';
  63. /**
  64. * how many processes will be created for the current worker
  65. * @var unknown_type
  66. */
  67. public $count = 1;
  68. /**
  69. * Set the real user of the current process . Needs appropriate privileges (usually root)
  70. * @var string
  71. */
  72. public $user = '';
  73. /**
  74. * If you do not want restart current worker processes, when received reload signal
  75. * just set reloadable = true
  76. * @var bool
  77. */
  78. public $reloadable = true;
  79. /**
  80. * when worker start, then run onWorkerStart
  81. * @var callback
  82. */
  83. public $onWorkerStart = null;
  84. /**
  85. * when client connect worker, onConnect will be run
  86. * @var callback
  87. */
  88. public $onConnect = null;
  89. /**
  90. * when worker recv data, onMessage will be run
  91. * @var callback
  92. */
  93. public $onMessage = null;
  94. /**
  95. * when connection closed, onClose will be run
  96. * @var callback
  97. */
  98. public $onClose = null;
  99. /**
  100. * when connection has error, onError will be run
  101. * @var unknown_type
  102. */
  103. public $onError = null;
  104. /**
  105. * when worker stop, which function will be run
  106. * @var callback
  107. */
  108. public $onWorkerStop = null;
  109. /**
  110. * tcp/udp
  111. * @var string
  112. */
  113. public $transport = 'tcp';
  114. /**
  115. * protocol
  116. * @var string
  117. */
  118. protected $_protocol = '';
  119. /**
  120. * if run as daemon
  121. * @var bool
  122. */
  123. public static $daemonize = false;
  124. /**
  125. * all output buffer (echo var_dump etc) will write to the file
  126. * @var string
  127. */
  128. public static $stdoutFile = '/dev/null';
  129. /**
  130. * pid file
  131. * @var string
  132. */
  133. public static $pidFile = '';
  134. /**
  135. * log file path
  136. * @var unknown_type
  137. */
  138. public static $logFile = '';
  139. /**
  140. * event loop
  141. * @var Select/Libevent
  142. */
  143. public static $globalEvent = null;
  144. /**
  145. * master process pid
  146. * @var int
  147. */
  148. protected static $_masterPid = 0;
  149. /**
  150. * stream socket of the worker
  151. * @var stream
  152. */
  153. protected $_mainSocket = null;
  154. /**
  155. * socket name example http://0.0.0.0:80
  156. * @var string
  157. */
  158. protected $_socketName = '';
  159. /**
  160. * context
  161. * @var context
  162. */
  163. protected $_context = null;
  164. /**
  165. * enable ssl or not
  166. * @var bool
  167. */
  168. protected $_enableSSL = false;
  169. /**
  170. * all instances of worker
  171. * @var array
  172. */
  173. protected static $_workers = array();
  174. /**
  175. * all workers and pids
  176. * @var array
  177. */
  178. protected static $_pidMap = array();
  179. /**
  180. * all processes to be restart [pid=>pid, pid=>pid]
  181. * @var array
  182. */
  183. protected static $_pidsToRestart = array();
  184. /**
  185. * current status
  186. * @var int
  187. */
  188. protected static $_status = self::STATUS_STARTING;
  189. /**
  190. * max length of $_workerName
  191. * @var int
  192. */
  193. protected static $_maxWorkerNameLength = 12;
  194. /**
  195. * max length of $_socketName
  196. * @var int
  197. */
  198. protected static $_maxSocketNameLength = 12;
  199. /**
  200. * max length of $user's name
  201. * @var int
  202. */
  203. protected static $_maxUserNameLength = 12;
  204. /**
  205. * the path of status file, witch will store status of processes
  206. * @var string
  207. */
  208. protected static $_statisticsFile = '';
  209. /**
  210. * start file path
  211. * @var string
  212. */
  213. protected static $_startFile = '';
  214. /**
  215. * global statistics
  216. * @var array
  217. */
  218. protected static $_globalStatistics = array(
  219. 'start_timestamp' => 0,
  220. 'worker_exit_info' => array()
  221. );
  222. /**
  223. * run all workers
  224. * @return void
  225. */
  226. public static function runAll()
  227. {
  228. self::init();
  229. self::parseCommand();
  230. self::daemonize();
  231. self::initWorkers();
  232. self::installSignal();
  233. self::displayUI();
  234. self::resetStd();
  235. self::saveMasterPid();
  236. self::forkWorkers();
  237. self::monitorWorkers();
  238. }
  239. /**
  240. * initialize the environment variables
  241. * @return void
  242. */
  243. public static function init()
  244. {
  245. ini_set('opcache.enable', false);
  246. if(empty(self::$pidFile))
  247. {
  248. $backtrace = debug_backtrace();
  249. self::$_startFile = $backtrace[count($backtrace)-1]['file'];
  250. self::$pidFile = sys_get_temp_dir()."/workerman.".str_replace('/', '_', self::$_startFile).".pid";
  251. }
  252. if(empty(self::$logFile))
  253. {
  254. self::$logFile = __DIR__ . '/../workerman.log';
  255. }
  256. self::$_status = self::STATUS_STARTING;
  257. self::$_globalStatistics['start_timestamp'] = time();
  258. self::$_statisticsFile = sys_get_temp_dir().'/workerman.status';
  259. self::setProcessTitle('WorkerMan: master process start_file=' . self::$_startFile);
  260. Timer::init();
  261. }
  262. /**
  263. * initialize the all the workers
  264. * @return void
  265. */
  266. protected static function initWorkers()
  267. {
  268. foreach(self::$_workers as $worker)
  269. {
  270. // if worker->name not set then use worker->_socketName as worker->name
  271. if(empty($worker->name))
  272. {
  273. $worker->name = 'none';
  274. }
  275. // get the max length of worker->name for formating status info
  276. $worker_name_length = strlen($worker->name);
  277. if(self::$_maxWorkerNameLength < $worker_name_length)
  278. {
  279. self::$_maxWorkerNameLength = $worker_name_length;
  280. }
  281. // get the max length of worker->_socketName
  282. $socket_name_length = strlen($worker->getSocketName());
  283. if(self::$_maxSocketNameLength < $socket_name_length)
  284. {
  285. self::$_maxSocketNameLength = $socket_name_length;
  286. }
  287. // get the max length user name
  288. if(empty($worker->user) || posix_getuid() !== 0)
  289. {
  290. $worker->user = self::getCurrentUser();
  291. }
  292. $user_name_length = strlen($worker->user);
  293. if(self::$_maxUserNameLength < $user_name_length)
  294. {
  295. self::$_maxUserNameLength = $user_name_length;
  296. }
  297. // listen
  298. $worker->listen();
  299. }
  300. }
  301. protected static function getCurrentUser()
  302. {
  303. $user_info = posix_getpwuid(posix_getuid());
  304. return $user_info['name'];
  305. }
  306. protected static function displayUI()
  307. {
  308. echo "\033[1A\n\033[K-----------------------\033[47;30m WORKERMAN \033[0m-----------------------------\n\033[0m";
  309. echo 'Workerman version:' . Worker::VERSION . " PHP version:".PHP_VERSION."\n";
  310. echo "------------------------\033[47;30m WORKERS \033[0m-------------------------------\n";
  311. echo "\033[47;30muser\033[0m",str_pad('', self::$_maxUserNameLength+2-strlen('user')), "\033[47;30mworker\033[0m",str_pad('', self::$_maxWorkerNameLength+2-strlen('worker')), "\033[47;30mlisten\033[0m",str_pad('', self::$_maxSocketNameLength+2-strlen('listen')), "\033[47;30mprocesses\033[0m \033[47;30m","status\033[0m\n";
  312. foreach(self::$_workers as $worker)
  313. {
  314. echo str_pad($worker->user, self::$_maxUserNameLength+2),str_pad($worker->name, self::$_maxWorkerNameLength+2),str_pad($worker->getSocketName(), self::$_maxSocketNameLength+2), str_pad(' '.$worker->count, 9), " \033[32;40m [OK] \033[0m\n";;
  315. }
  316. echo "----------------------------------------------------------------\n";
  317. }
  318. /**
  319. * php yourfile.php start | stop | restart | reload | status
  320. * @return void
  321. */
  322. public static function parseCommand()
  323. {
  324. // check command
  325. global $argv;
  326. $start_file = $argv[0];
  327. if(!isset($argv[1]))
  328. {
  329. exit("Usage: php yourfile.php {start|stop|restart|reload|status}\n");
  330. }
  331. $command = trim($argv[1]);
  332. $command2 = isset($argv[2]) ? $argv[2] : '';
  333. self::log("Workerman[$start_file] $command");
  334. // check if master process is running
  335. $master_pid = @file_get_contents(self::$pidFile);
  336. $master_is_alive = $master_pid && @posix_kill($master_pid, 0);
  337. if($master_is_alive)
  338. {
  339. if($command === 'start')
  340. {
  341. self::log("Workerman[$start_file] is running");
  342. }
  343. }
  344. elseif($command !== 'start' && $command !== 'restart')
  345. {
  346. self::log("Workerman[$start_file] not run");
  347. }
  348. switch($command)
  349. {
  350. // start workerman
  351. case 'start':
  352. if($command2 == '-d')
  353. {
  354. Worker::$daemonize = true;
  355. }
  356. break;
  357. // show status of workerman
  358. case 'status':
  359. // try to delete the statistics file , avoid read dirty data
  360. if(is_file(self::$_statisticsFile))
  361. {
  362. @unlink(self::$_statisticsFile);
  363. }
  364. // send SIGUSR2 to master process ,then master process will send SIGUSR2 to all children processes
  365. // all processes will write statistics data to statistics file
  366. posix_kill($master_pid, SIGUSR2);
  367. // wait all processes wirte statistics data
  368. usleep(100000);
  369. // display statistics file
  370. readfile(self::$_statisticsFile);
  371. exit(0);
  372. // restart workerman
  373. case 'restart':
  374. // stop workeran
  375. case 'stop':
  376. self::log("Workerman[$start_file] is stoping ...");
  377. // send SIGINT to master process, master process will stop all children process and exit
  378. $master_pid && posix_kill($master_pid, SIGINT);
  379. // if $timeout seconds master process not exit then dispaly stop failure
  380. $timeout = 5;
  381. // a recording start time
  382. $start_time = time();
  383. while(1)
  384. {
  385. $master_is_alive = $master_pid && posix_kill($master_pid, 0);
  386. if($master_is_alive)
  387. {
  388. // check whether has timed out
  389. if(time() - $start_time >= $timeout)
  390. {
  391. self::log("Workerman[$start_file] stop fail");
  392. exit;
  393. }
  394. // avoid the cost of CPU time, sleep for a while
  395. usleep(10000);
  396. continue;
  397. }
  398. self::log("Workerman[$start_file] stop success");
  399. if($command === 'stop')
  400. {
  401. exit(0);
  402. }
  403. if($command2 == '-d')
  404. {
  405. Worker::$daemonize = true;
  406. }
  407. break;
  408. }
  409. break;
  410. // reload workerman
  411. case 'reload':
  412. posix_kill($master_pid, SIGUSR1);
  413. self::log("Workerman[$start_file] reload");
  414. exit;
  415. // unknow command
  416. default :
  417. exit("Usage: php yourfile.php {start|stop|restart|reload|status}\n");
  418. }
  419. }
  420. /**
  421. * installs signal handlers for master
  422. * @return void
  423. */
  424. protected static function installSignal()
  425. {
  426. // stop
  427. pcntl_signal(SIGINT, array('\Workerman\Worker', 'signalHandler'), false);
  428. // reload
  429. pcntl_signal(SIGUSR1, array('\Workerman\Worker', 'signalHandler'), false);
  430. // status
  431. pcntl_signal(SIGUSR2, array('\Workerman\Worker', 'signalHandler'), false);
  432. // ignore
  433. pcntl_signal(SIGPIPE, SIG_IGN, false);
  434. }
  435. /**
  436. * reinstall signal handlers for workers
  437. * @return void
  438. */
  439. protected static function reinstallSignal()
  440. {
  441. // uninstall stop signal handler
  442. pcntl_signal(SIGINT, SIG_IGN, false);
  443. // uninstall reload signal handler
  444. pcntl_signal(SIGUSR1, SIG_IGN, false);
  445. // uninstall status signal handler
  446. pcntl_signal(SIGUSR2, SIG_IGN, false);
  447. // reinstall stop signal handler
  448. self::$globalEvent->add(SIGINT, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler'));
  449. // uninstall reload signal handler
  450. self::$globalEvent->add(SIGUSR1, EventInterface::EV_SIGNAL,array('\Workerman\Worker', 'signalHandler'));
  451. // uninstall status signal handler
  452. self::$globalEvent->add(SIGUSR2, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler'));
  453. }
  454. /**
  455. * signal handler
  456. * @param int $signal
  457. */
  458. public static function signalHandler($signal)
  459. {
  460. switch($signal)
  461. {
  462. // stop
  463. case SIGINT:
  464. self::stopAll();
  465. break;
  466. // reload
  467. case SIGUSR1:
  468. self::$_pidsToRestart = self::getAllWorkerPids();;
  469. self::reload();
  470. break;
  471. // show status
  472. case SIGUSR2:
  473. self::writeStatisticsToStatusFile();
  474. break;
  475. }
  476. }
  477. /**
  478. * run workerman as daemon
  479. * @throws Exception
  480. */
  481. protected static function daemonize()
  482. {
  483. if(!self::$daemonize)
  484. {
  485. return;
  486. }
  487. umask(0);
  488. $pid = pcntl_fork();
  489. if(-1 == $pid)
  490. {
  491. throw new Exception('fork fail');
  492. }
  493. elseif($pid > 0)
  494. {
  495. exit(0);
  496. }
  497. if(-1 == posix_setsid())
  498. {
  499. throw new Exception("setsid fail");
  500. }
  501. // fork again avoid SVR4 system regain the control of terminal
  502. $pid = pcntl_fork();
  503. if(-1 == $pid)
  504. {
  505. throw new Exception("fork fail");
  506. }
  507. elseif(0 !== $pid)
  508. {
  509. exit(0);
  510. }
  511. }
  512. /**
  513. * redirecting output
  514. * @throws Exception
  515. */
  516. protected static function resetStd()
  517. {
  518. if(!self::$daemonize)
  519. {
  520. return;
  521. }
  522. global $STDOUT, $STDERR;
  523. $handle = fopen(self::$stdoutFile,"a");
  524. if($handle)
  525. {
  526. unset($handle);
  527. @fclose(STDOUT);
  528. @fclose(STDERR);
  529. $STDOUT = fopen(self::$stdoutFile,"a");
  530. $STDERR = fopen(self::$stdoutFile,"a");
  531. }
  532. else
  533. {
  534. throw new Exception('can not open stdoutFile ' . self::$stdoutFile);
  535. }
  536. }
  537. /**
  538. * save the pid of master for later stop/reload/restart/status command
  539. * @throws Exception
  540. */
  541. protected static function saveMasterPid()
  542. {
  543. self::$_masterPid = posix_getpid();
  544. if(false === @file_put_contents(self::$pidFile, self::$_masterPid))
  545. {
  546. throw new Exception('can not save pid to ' . self::$pidFile);
  547. }
  548. }
  549. /**
  550. * get all pids of workers
  551. * @return array
  552. */
  553. protected static function getAllWorkerPids()
  554. {
  555. $pid_array = array();
  556. foreach(self::$_pidMap as $worker_pid_array)
  557. {
  558. foreach($worker_pid_array as $worker_pid)
  559. {
  560. $pid_array[$worker_pid] = $worker_pid;
  561. }
  562. }
  563. return $pid_array;
  564. }
  565. /**
  566. * fork worker processes
  567. * @return void
  568. */
  569. protected static function forkWorkers()
  570. {
  571. foreach(self::$_workers as $worker)
  572. {
  573. // check worker->name etc
  574. if(self::$_status === self::STATUS_STARTING)
  575. {
  576. // if worker->name not set then use worker->_socketName as worker->name
  577. if(empty($worker->name))
  578. {
  579. $worker->name = $worker->getSocketName();
  580. }
  581. // get the max length of worker->name for formating status info
  582. $worker_name_length = strlen($worker->name);
  583. if(self::$_maxWorkerNameLength < $worker_name_length)
  584. {
  585. self::$_maxWorkerNameLength = $worker_name_length;
  586. }
  587. }
  588. // create processes
  589. while(count(self::$_pidMap[$worker->workerId]) < $worker->count)
  590. {
  591. self::forkOneWorker($worker);
  592. }
  593. }
  594. }
  595. /**
  596. * fork one worker and run it
  597. * @param Worker $worker
  598. * @throws Exception
  599. */
  600. protected static function forkOneWorker($worker)
  601. {
  602. $pid = pcntl_fork();
  603. if($pid > 0)
  604. {
  605. self::$_pidMap[$worker->workerId][$pid] = $pid;
  606. }
  607. elseif(0 === $pid)
  608. {
  609. self::$_pidMap = array();
  610. self::$_workers = array($worker->workerId => $worker);
  611. Timer::delAll();
  612. self::setProcessTitle('WorkerMan: worker process ' . $worker->name . ' ' . $worker->getSocketName());
  613. self::setProcessUser($worker->user);
  614. $worker->run();
  615. exit(250);
  616. }
  617. else
  618. {
  619. throw new Exception("forkOneWorker fail");
  620. }
  621. }
  622. /**
  623. * set current process user
  624. * @return void
  625. */
  626. protected static function setProcessUser($user_name)
  627. {
  628. if(empty($user_name) || posix_getuid() !== 0)
  629. {
  630. return;
  631. }
  632. $user_info = posix_getpwnam($user_name);
  633. if($user_info['uid'] != posix_getuid() || $user_info['gid'] != posix_getgid())
  634. {
  635. if(!posix_setgid($user_info['gid']) || !posix_setuid($user_info['uid']))
  636. {
  637. self::log( 'Notice : Can not run woker as '.$user_name." , You shuld be root\n", true);
  638. }
  639. }
  640. }
  641. /**
  642. * set current process title
  643. * @param string $title
  644. * @return void
  645. */
  646. protected static function setProcessTitle($title)
  647. {
  648. // >=php 5.5
  649. if (function_exists('cli_set_process_title'))
  650. {
  651. @cli_set_process_title($title);
  652. }
  653. // 需要扩展
  654. elseif(extension_loaded('proctitle') && function_exists('setproctitle'))
  655. {
  656. @setproctitle($title);
  657. }
  658. }
  659. /**
  660. * wait for the child process exit
  661. * @return void
  662. */
  663. protected static function monitorWorkers()
  664. {
  665. self::$_status = self::STATUS_RUNNING;
  666. while(1)
  667. {
  668. // calls signal handlers for pending signals
  669. pcntl_signal_dispatch();
  670. // suspends execution of the current process until a child has exited or a signal is delivered
  671. $status = 0;
  672. $pid = pcntl_wait($status, WUNTRACED);
  673. if($pid > 0)
  674. {
  675. foreach(self::$_pidMap as $worker_id => $worker_pid_array)
  676. {
  677. if(isset($worker_pid_array[$pid]))
  678. {
  679. $worker = self::$_workers[$worker_id];
  680. // check status
  681. if($status !== 0)
  682. {
  683. self::log("worker[".$worker->name.":$pid] exit with status $status");
  684. }
  685. // statistics
  686. if(!isset(self::$_globalStatistics['worker_exit_info'][$worker_id][$status]))
  687. {
  688. self::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0;
  689. }
  690. self::$_globalStatistics['worker_exit_info'][$worker_id][$status]++;
  691. // clear pid info
  692. unset(self::$_pidMap[$worker_id][$pid]);
  693. // if realoding, continue
  694. if(isset(self::$_pidsToRestart[$pid]))
  695. {
  696. unset(self::$_pidsToRestart[$pid]);
  697. self::reload();
  698. }
  699. break;
  700. }
  701. }
  702. // workerman is still running
  703. if(self::$_status !== self::STATUS_SHUTDOWN)
  704. {
  705. self::forkWorkers();
  706. }
  707. else
  708. {
  709. // workerman is shuting down
  710. if(!self::getAllWorkerPids())
  711. {
  712. self::exitAndClearAll();
  713. }
  714. }
  715. }
  716. else
  717. {
  718. if(self::$_status === self::STATUS_SHUTDOWN && !self::getAllWorkerPids())
  719. {
  720. self::exitAndClearAll();
  721. }
  722. }
  723. }
  724. }
  725. /**
  726. * exit
  727. */
  728. protected static function exitAndClearAll()
  729. {
  730. @unlink(self::$pidFile);
  731. self::log("Workerman[".basename(self::$_startFile)."] has been stopped");
  732. exit(0);
  733. }
  734. /**
  735. * reload workerman, gracefully restart child processes one by one
  736. * @return void
  737. */
  738. protected static function reload()
  739. {
  740. // for master process
  741. if(self::$_masterPid === posix_getpid())
  742. {
  743. // set status
  744. if(self::$_status !== self::STATUS_RELOADING && self::$_status !== self::STATUS_SHUTDOWN)
  745. {
  746. self::log("Workerman[".basename(self::$_startFile)."] reloading");
  747. self::$_status = self::STATUS_RELOADING;
  748. }
  749. $reloadable_pid_array = array();
  750. foreach(self::$_pidMap as $worker_id =>$worker_pid_array)
  751. {
  752. $worker = self::$_workers[$worker_id];
  753. if($worker->reloadable)
  754. {
  755. foreach($worker_pid_array as $pid)
  756. {
  757. $reloadable_pid_array[$pid] = $pid;
  758. }
  759. }
  760. }
  761. self::$_pidsToRestart = array_intersect(self::$_pidsToRestart , $reloadable_pid_array);
  762. // reload complete
  763. if(empty(self::$_pidsToRestart))
  764. {
  765. if(self::$_status !== self::STATUS_SHUTDOWN)
  766. {
  767. self::$_status = self::STATUS_RUNNING;
  768. }
  769. return;
  770. }
  771. // continue reload
  772. $one_worker_pid = current(self::$_pidsToRestart );
  773. posix_kill($one_worker_pid, SIGUSR1);
  774. Timer::add(self::KILL_WORKER_TIMER_TIME, 'posix_kill', array($one_worker_pid, SIGKILL), false);
  775. }
  776. // for children process
  777. else
  778. {
  779. $worker = current(self::$_workers);
  780. if($worker->reloadable)
  781. {
  782. self::stopAll();
  783. }
  784. }
  785. }
  786. /**
  787. * stop all workers
  788. * @return void
  789. */
  790. public static function stopAll()
  791. {
  792. self::$_status = self::STATUS_SHUTDOWN;
  793. // for master process
  794. if(self::$_masterPid === posix_getpid())
  795. {
  796. self::log("Workerman[".basename(self::$_startFile)."] Stopping ...");
  797. $worker_pid_array = self::getAllWorkerPids();
  798. foreach($worker_pid_array as $worker_pid)
  799. {
  800. posix_kill($worker_pid, SIGINT);
  801. Timer::add(self::KILL_WORKER_TIMER_TIME, 'posix_kill', array($worker_pid, SIGKILL),false);
  802. }
  803. }
  804. // for worker process
  805. else
  806. {
  807. foreach(self::$_workers as $worker)
  808. {
  809. $worker->stop();
  810. }
  811. exit(0);
  812. }
  813. }
  814. /**
  815. * for workermand status command
  816. * @return void
  817. */
  818. protected static function writeStatisticsToStatusFile()
  819. {
  820. // for master process
  821. if(self::$_masterPid === posix_getpid())
  822. {
  823. $loadavg = sys_getloadavg();
  824. file_put_contents(self::$_statisticsFile, "---------------------------------------GLOBAL STATUS--------------------------------------------\n");
  825. file_put_contents(self::$_statisticsFile, 'Workerman version:' . Worker::VERSION . " PHP version:".PHP_VERSION."\n", FILE_APPEND);
  826. file_put_contents(self::$_statisticsFile, 'start time:'. date('Y-m-d H:i:s', self::$_globalStatistics['start_timestamp']).' run ' . floor((time()-self::$_globalStatistics['start_timestamp'])/(24*60*60)). ' days ' . floor(((time()-self::$_globalStatistics['start_timestamp'])%(24*60*60))/(60*60)) . " hours \n", FILE_APPEND);
  827. file_put_contents(self::$_statisticsFile, 'load average: ' . implode(", ", $loadavg) . "\n", FILE_APPEND);
  828. file_put_contents(self::$_statisticsFile, count(self::$_pidMap) . ' workers ' . count(self::getAllWorkerPids())." processes\n", FILE_APPEND);
  829. file_put_contents(self::$_statisticsFile, str_pad('worker_name', self::$_maxWorkerNameLength) . " exit_status exit_count\n", FILE_APPEND);
  830. foreach(self::$_pidMap as $worker_id =>$worker_pid_array)
  831. {
  832. $worker = self::$_workers[$worker_id];
  833. if(isset(self::$_globalStatistics['worker_exit_info'][$worker_id]))
  834. {
  835. foreach(self::$_globalStatistics['worker_exit_info'][$worker_id] as $worker_exit_status=>$worker_exit_count)
  836. {
  837. file_put_contents(self::$_statisticsFile, str_pad($worker->name, self::$_maxWorkerNameLength) . " " . str_pad($worker_exit_status, 16). " $worker_exit_count\n", FILE_APPEND);
  838. }
  839. }
  840. else
  841. {
  842. file_put_contents(self::$_statisticsFile, str_pad($worker->name, self::$_maxWorkerNameLength) . " " . str_pad(0, 16). " 0\n", FILE_APPEND);
  843. }
  844. }
  845. file_put_contents(self::$_statisticsFile, "---------------------------------------PROCESS STATUS-------------------------------------------\n", FILE_APPEND);
  846. file_put_contents(self::$_statisticsFile, "pid\tmemory ".str_pad('listening', self::$_maxSocketNameLength)." ".str_pad('worker_name', self::$_maxWorkerNameLength)." ".str_pad('total_request', 13)." ".str_pad('send_fail', 9)." ".str_pad('throw_exception', 15)."\n", FILE_APPEND);
  847. chmod(self::$_statisticsFile, 0722);
  848. foreach(self::getAllWorkerPids() as $worker_pid)
  849. {
  850. posix_kill($worker_pid, SIGUSR2);
  851. }
  852. return;
  853. }
  854. // for worker process
  855. $worker = current(self::$_workers);
  856. $wrker_status_str = posix_getpid()."\t".str_pad(round(memory_get_usage()/(1024*1024),2)."M", 7)." " .str_pad($worker->getSocketName(), self::$_maxSocketNameLength) ." ".str_pad(($worker->name == $worker->getSocketName() ? 'none' : $worker->name), self::$_maxWorkerNameLength)." ";
  857. $wrker_status_str .= str_pad(ConnectionInterface::$statistics['total_request'], 14)." ".str_pad(ConnectionInterface::$statistics['send_fail'],9)." ".str_pad(ConnectionInterface::$statistics['throw_exception'],15)."\n";
  858. file_put_contents(self::$_statisticsFile, $wrker_status_str, FILE_APPEND);
  859. }
  860. /**
  861. * log
  862. * @param string $msg
  863. * @return void
  864. */
  865. protected static function log($msg)
  866. {
  867. $msg = $msg."\n";
  868. if(self::$_status === self::STATUS_STARTING || !self::$daemonize)
  869. {
  870. echo $msg;
  871. }
  872. file_put_contents(self::$logFile, date('Y-m-d H:i:s') . " " . $msg, FILE_APPEND);
  873. }
  874. /**
  875. * create a worker
  876. * @param string $socket_name
  877. * @return void
  878. */
  879. public function __construct($socket_name = '', $context_option = array())
  880. {
  881. $this->workerId = spl_object_hash($this);
  882. self::$_workers[$this->workerId] = $this;
  883. self::$_pidMap[$this->workerId] = array();
  884. if($socket_name)
  885. {
  886. $this->_socketName = $socket_name;
  887. if(!isset($context_option['socket']['backlog']))
  888. {
  889. $context_option['socket']['backlog'] = self::DEFAUL_BACKLOG;
  890. }
  891. $this->_context = stream_context_create($context_option);
  892. }
  893. }
  894. /**
  895. * listen and bind socket
  896. * @throws Exception
  897. */
  898. public function listen()
  899. {
  900. if(!$this->_socketName)
  901. {
  902. return;
  903. }
  904. list($scheme, $address) = explode(':', $this->_socketName, 2);
  905. if($scheme != 'tcp' && $scheme != 'udp')
  906. {
  907. $scheme = ucfirst($scheme);
  908. $this->_protocol = '\\Protocols\\'.$scheme;
  909. if(!class_exists($this->_protocol))
  910. {
  911. $this->_protocol = "\\Workerman\\Protocols\\$scheme";
  912. if(!class_exists($this->_protocol))
  913. {
  914. throw new Exception("class \\Protocols\\$scheme not exist");
  915. }
  916. }
  917. }
  918. elseif($scheme === 'udp')
  919. {
  920. $this->transport = 'udp';
  921. }
  922. $flags = $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
  923. if($this->_enableSSL)
  924. {
  925. if($this->transport == 'udp')
  926. {
  927. throw new Exception('udp do not support ssl');
  928. }
  929. $this->_mainSocket = stream_socket_server("ssl:".$address, $errno, $errmsg, $flags, $this->_context);
  930. @stream_socket_enable_crypto($this->_mainSocket, false);
  931. }
  932. else
  933. {
  934. $this->_mainSocket = stream_socket_server($this->transport.":".$address, $errno, $errmsg, $flags, $this->_context);
  935. }
  936. if(!$this->_mainSocket)
  937. {
  938. throw new Exception($errmsg);
  939. }
  940. stream_set_blocking($this->_mainSocket, 0);
  941. if(self::$globalEvent)
  942. {
  943. if($this->transport !== 'udp')
  944. {
  945. self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection'));
  946. }
  947. else
  948. {
  949. self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection'));
  950. }
  951. }
  952. }
  953. /**
  954. * get socket name
  955. * @return string
  956. */
  957. public function getSocketName()
  958. {
  959. return $this->_socketName ? $this->_socketName : 'none';
  960. }
  961. /**
  962. * enable SSL
  963. * @param array $option @see http://php.net/manual/zh/context.ssl.php
  964. * example $option = array('local_cert' => '/your/path/file.pem', 'passphrase' => 'password', 'allow_self_signed' => true, 'verify_peer' => false)
  965. * @return void
  966. */
  967. public function enableSSL(array $option)
  968. {
  969. $this->_enableSSL = true;
  970. foreach($option as $key => $value)
  971. {
  972. stream_context_set_option($this->_context, 'ssl', $key, $value);
  973. }
  974. }
  975. /**
  976. * run the current worker
  977. */
  978. public function run()
  979. {
  980. if(!self::$globalEvent)
  981. {
  982. if(extension_loaded('libevent'))
  983. {
  984. self::$globalEvent = new Libevent();
  985. }
  986. else
  987. {
  988. self::$globalEvent = new Select();
  989. }
  990. if($this->_socketName)
  991. {
  992. if($this->transport !== 'udp')
  993. {
  994. self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection'));
  995. }
  996. else
  997. {
  998. self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection'));
  999. }
  1000. }
  1001. }
  1002. self::reinstallSignal();
  1003. Timer::init(self::$globalEvent);
  1004. if($this->onWorkerStart)
  1005. {
  1006. call_user_func($this->onWorkerStart, $this);
  1007. }
  1008. self::$globalEvent->loop();
  1009. }
  1010. /**
  1011. * stop the current worker
  1012. * @return void
  1013. */
  1014. public function stop()
  1015. {
  1016. if($this->onWorkerStop)
  1017. {
  1018. call_user_func($this->onWorkerStop, $this);
  1019. }
  1020. self::$globalEvent->del($this->_mainSocket, EventInterface::EV_READ);
  1021. @fclose($this->_mainSocket);
  1022. }
  1023. /**
  1024. * accept a connection of client
  1025. * @param resources $socket
  1026. * @return void
  1027. */
  1028. public function acceptConnection($socket)
  1029. {
  1030. $new_socket = @stream_socket_accept($socket, 0);
  1031. if(false === $new_socket)
  1032. {
  1033. return;
  1034. }
  1035. if($this->_enableSSL)
  1036. {
  1037. // block the connection until SSL is done
  1038. stream_set_blocking ($socket, true);
  1039. stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_SSLv3_SERVER);
  1040. //unblock connection
  1041. stream_set_blocking ($socket, false);
  1042. }
  1043. $connection = new TcpConnection($new_socket);
  1044. $connection->protocol = $this->_protocol;
  1045. $connection->onMessage = $this->onMessage;
  1046. $connection->onClose = $this->onClose;
  1047. $connection->onError = $this->onError;
  1048. if($this->onConnect)
  1049. {
  1050. try
  1051. {
  1052. call_user_func($this->onConnect, $connection);
  1053. }
  1054. catch(Exception $e)
  1055. {
  1056. ConnectionInterface::$statistics['throw_exception']++;
  1057. self::log($e);
  1058. }
  1059. }
  1060. }
  1061. /**
  1062. * deall udp package
  1063. * @param resource $socket
  1064. */
  1065. public function acceptUdpConnection($socket)
  1066. {
  1067. $recv_buffer = stream_socket_recvfrom($socket , self::MAX_UDP_PACKEG_SIZE, 0, $remote_address);
  1068. if(false === $recv_buffer || empty($remote_address))
  1069. {
  1070. return false;
  1071. }
  1072. $connection = new UdpConnection($socket, $remote_address);
  1073. if($this->onMessage)
  1074. {
  1075. $parser = $this->_protocol;
  1076. try
  1077. {
  1078. ConnectionInterface::$statistics['total_request']++;
  1079. call_user_func($this->onMessage, $connection, $parser::decode($recv_buffer, $connection));
  1080. }
  1081. catch(Exception $e)
  1082. {
  1083. ConnectionInterface::$statistics['throw_exception']++;
  1084. }
  1085. }
  1086. }
  1087. }