server.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <?php
  2. /**
  3. * 服务管理类
  4. */
  5. class Server
  6. {
  7. /**
  8. * 配置数据
  9. */
  10. protected $cfg;
  11. /**
  12. * 服务器的Socket资源句柄
  13. * @var resource
  14. */
  15. protected $fd;
  16. /**
  17. * 进程锁文件句柄
  18. * @var resource
  19. */
  20. protected $lockFile;
  21. /**
  22. * 客户列表
  23. * @var array
  24. */
  25. protected $clients;
  26. /**
  27. * select的读文件句柄列表
  28. * @var array
  29. */
  30. protected $reading;
  31. /**
  32. * select的写文件句柄列表
  33. * @var array
  34. */
  35. protected $writing;
  36. /**
  37. * 需要退出的标志
  38. * @var bool
  39. */
  40. protected $quiting;
  41. /**
  42. * 需要重启的标志
  43. * @var bool
  44. */
  45. protected $restarting;
  46. /**
  47. * 构造函数
  48. * @param array $cfg 配置数据
  49. */
  50. public function __construct($cfg)
  51. {
  52. $this->cfg = $cfg;
  53. $this->clients = [];
  54. $this->reading = [];
  55. $this->writing = [];
  56. $this->quiting = false;
  57. $this->restarting = false;
  58. }
  59. /**
  60. * 析构函数
  61. */
  62. public function __destruct()
  63. {
  64. if (isset($this->fd)) {
  65. fclose($this->fd);
  66. }
  67. }
  68. /**
  69. * 开始服务
  70. */
  71. public function start()
  72. {
  73. //创建锁文件
  74. $lockFilePath = "{$this->cfg['pid_path']}/{$this->cfg['server_name']}.{$this->cfg['port']}.pid";
  75. $this->lockFile = run_single_instance($lockFilePath);
  76. //修改进程名
  77. cli_set_process_title("{$this->cfg['php_bin_path']} -f {$_SERVER['argv'][0]} [{$this->cfg['server_name']}]");
  78. //创建socket
  79. $this->fd = stream_socket_server("tcp://{$this->cfg['host']}:{$this->cfg['port']}", $errno, $error);
  80. if ($this->fd === false) {
  81. debug_log("stream_socket_server() error:#{$errno},{$error}", 'ERROR', LOG_NAME);
  82. exit(1);
  83. }
  84. $this->reading[(int)$this->fd] = $this->fd;
  85. debug_log("Server start at {$this->cfg['host']}:{$this->cfg['port']}", 'DEBUG', LOG_NAME);
  86. //设置信号回调
  87. pcntl_signal(SIGTERM, [$this, 'sigHandle']);
  88. pcntl_signal(SIGUSR1, [$this, 'sigHandle']);
  89. pcntl_signal(SIGPIPE, SIG_IGN);
  90. }
  91. /**
  92. * 接收新连接
  93. */
  94. protected function acceptNewClient()
  95. {
  96. $fd = stream_socket_accept($this->fd);
  97. if (!$fd) {
  98. debug_log("stream_socket_accept() error", 'ERROR', LOG_NAME);
  99. return;
  100. }
  101. $cli = new Client($fd, $this->cfg);
  102. stream_set_blocking($fd, 0);
  103. $this->clients[(int)$fd] = $cli;
  104. $this->reading[(int)$fd] = $fd;
  105. $cli->onConnect();
  106. }
  107. /**
  108. * 删除连接
  109. */
  110. public function deleteClient($fd)
  111. {
  112. $cli = isset($this->clients[(int)$fd]) ? $this->clients[(int)$fd] : null;
  113. if ($cli != null) {
  114. $cli->onClose();
  115. }
  116. if (isset($this->writing[(int)$fd])) {
  117. unset($this->writing[(int)$fd]);
  118. }
  119. if (isset($this->reading[(int)$fd])) {
  120. unset($this->reading[(int)$fd]);
  121. }
  122. if (isset($this->clients[(int)$fd])) {
  123. unset($this->clients[(int)$fd]);
  124. }
  125. fclose($fd);
  126. }
  127. /**
  128. * 从指定连接读数据
  129. * @param resource $fd 要读取的客户连接socket资源
  130. */
  131. protected function readClient($fd)
  132. {
  133. if (!isset($this->clients[(int)$fd])) {
  134. $this->deleteClient($fd);
  135. return;
  136. }
  137. $cli = $this->clients[(int)$fd];
  138. $data = fread($fd, $this->cfg['max_read_size']);
  139. if ($data === false) {
  140. $this->deleteClient($fd);
  141. debug_log("socket_read({$fd}) error, client:" . $cli->peer(), 'ERROR', LOG_NAME);
  142. } elseif ($data === '') {
  143. $this->deleteClient($fd);
  144. } else {
  145. $cli->onRead($data);
  146. }
  147. }
  148. /**
  149. * 写消息给指定的客户连接
  150. * @param resource $fd 要写消息的客户连接socket资源
  151. */
  152. protected function writeClient($fd)
  153. {
  154. if (!isset($this->clients[(int)$fd])) {
  155. $this->deleteClient($fd);
  156. return;
  157. }
  158. $cli = $this->clients[(int)$fd];
  159. $data = $cli->prepareWrite();
  160. if ($data === false) {
  161. unset($this->writing[(int)$fd]);
  162. } else {
  163. $n = fwrite($fd, $data);
  164. if ($n === false) {
  165. $this->deleteClient($fd);
  166. debug_log("socket_write({$fd}) error, client:" . $cli->peer(), 'ERROR', LOG_NAME);
  167. } else {
  168. echo "{$fd} write:" . $data . "\n";
  169. $cli->onWrite($n);
  170. }
  171. }
  172. }
  173. /**
  174. * 将指定的客户连接设置为需要写数据的状态
  175. */
  176. public function setClientWriting($fd)
  177. {
  178. $this->writing[(int)$fd] = $fd;
  179. }
  180. /**
  181. * 退出
  182. */
  183. public function quit()
  184. {
  185. debug_log("Server is quiting", 'WARNING', LOG_NAME);
  186. foreach ($this->clients as $cli) {
  187. $this->deleteClient($cli->fd());
  188. }
  189. flock($this->lockFile, LOCK_UN);
  190. fclose($this->lockFile);
  191. exit(0);
  192. }
  193. /**
  194. * 重启
  195. */
  196. public function restart()
  197. {
  198. debug_log("Server is restarting", 'WARNING', LOG_NAME);
  199. foreach ($this->clients as $cli) {
  200. $this->deleteClient($cli->fd());
  201. }
  202. fclose($this->fd);
  203. flock($this->lockFile, LOCK_UN);
  204. fclose($this->lockFile);
  205. pcntl_exec($this->cfg['php_bin_path'], $_SERVER['argv']);
  206. debug_log("Server restart failed", 'ERROR', LOG_NAME);
  207. exit(1);
  208. }
  209. /**
  210. * 开始服务
  211. */
  212. public function run()
  213. {
  214. while (true) {
  215. $rList = array_values($this->reading); //必须复制一份
  216. $wList = array_values($this->writing); //必须复制一份
  217. $eList = [];
  218. $n = 0;
  219. list($sec, $msec) = GET_TIMER()->getWaitTime();
  220. declare (ticks = 1) {
  221. $n = stream_select($rList, $wList, $eList, $sec, $msec);
  222. }
  223. if ($n === false) {
  224. if ($this->quiting) {
  225. $this->quit();
  226. } elseif ($this->restarting) {
  227. $this->restart();
  228. } else {
  229. debug_log("stream_select() error", 'ERROR', LOG_NAME);
  230. exit(1);
  231. }
  232. } elseif ($n > 0) {
  233. foreach ($rList as $fd) {
  234. if ($fd == $this->fd) {
  235. $this->acceptNewClient();
  236. } else {
  237. $this->readClient($fd);
  238. }
  239. }
  240. foreach ($wList as $fd) {
  241. $this->writeClient($fd);
  242. }
  243. } else {
  244. }
  245. //定时器的循环检测代码
  246. GET_TIMER()->interval();
  247. }
  248. }
  249. /**
  250. * 信号回调
  251. */
  252. public function sigHandle($signo)
  253. {
  254. switch ($signo)
  255. {
  256. //退出
  257. case SIGTERM:
  258. $this->quiting = true;
  259. break;
  260. //重启
  261. case SIGUSR1:
  262. $this->restarting = true;
  263. break;
  264. }
  265. }
  266. }