Swoole 整合成一个小框架

2019年5月12日 198点热度 0人点赞 0条评论

第 82 篇文章

这是关于 Swoole 学习的第六篇文章:Swoole 整合成一个小框架。

概述

写了关于 Swoole 入门的 5 篇文章后,增加了不少的关注者,也得到了一些大佬的鼓励和建议,也有很多关注者都加了微信好友,交流之后发现一些朋友比我优秀还比我努力。

发现持续写文章真的不是件容易的事,担心别人认为没价值,担心想法太幼稚或有漏洞被别人笑话,担心肚子里墨水太少,写不出来... 

知道自己思路还不够清晰,逻辑还不够严谨,告诉自己没关系,一切都会好起来的,逆境才能成长嘛,敢写就是好的开始,以此来激励自己持续的学习和思考。

跑题了,说回正题。

这篇文章其实是读者的小小要求,事情是这样的:

读者:“亮哥,看了你的文章很有收获,将文章 Demo 放在本地直接就能运行了,太感谢了”

本人:“哈哈。。。有收获就好,感谢支持 ~ ”

读者:“我有一个小小的要求,现在每个文件都是独立的,我想部署到生产环境,想操作上更便捷,有日志...”

本人:“你说的不是框架吗?现在有很多现成的,看 Swoole 官网推荐的 Swoft、EasySwoole、MixPHP 等。详细的参考这个地址:https://wiki.swoole.com/wiki/page/p-open_source.html”

读者:“看了,发现文件太多了,看不懂,你能帮忙讲解下吗?”

本人:“What?我也是入门呀,要不我搞个简单的轮子吧”

......

于是就有了这篇文章,正好也是对前面 5 篇文章的复习吧。

效果

图片

图片

命令如下:

  • php index.php 可以查看到上图

  • php index.php start 开启服务(Debug模式)

  • php index.php start -d 开启服务(Daemon模式)

  • php index.php status 查看服务状态

  • php index.php reload 服务热加载

  • php index.php stop 关闭服务

index.php 这是文件名称,大家叫什么都可以。

目录结构如下:

  1. ├─ controller

  2. ├── ...

  3. ├─ client

  4. ├─ websocket

  5. ├── ...

  6. ├─ tcp

  7. ├── ...

  8. ├─ server

  9. ├─ config

  10. ├── config.php

  11. ├─ core

  12. ├── Common.php

  13. ├── Core.php

  14. ├── HandlerException.php

  15. ├─ log -- 需要 读/写 权限

  16. ├── ...

  17. ├─ index.php

目前就这几个文件,后期研究新的知识点会直接集成到这里面。

说说实现了什么:

1、启动了 WebSocket、HTTP、TCP、UDP 等服务。

2、WebSocket 例子,在 client/websocket 文件夹,实现了视频弹幕。

3、HTTP 例子,在浏览器直接访问:http://ip:port,逻辑代码在 controller 文件夹。

4、TCP 例子,在 client/tcp 文件夹。

5、UDP 例子,直接运行 netcat-u ip port 即可。

6、相关配置,在 server/config 文件夹。

代码

放不全,就放一个主要的文件吧(Core.php)。

  1. <?php


  2. if (!defined('SERVER_PATH')) exit("No Access");


  3. class Core

  4. {

  5. private static $serv;


  6. public function __construct() {

  7. set_error_handler(['HandlerException', 'appError']);

  8. register_shutdown_function(['HandlerException', 'fatalError']);

  9. }


  10. public static function run() {

  11. static::checkCli();

  12. static::checkExtension();

  13. static::showUsageUI();

  14. static::parseCommand();

  15. }


  16. protected static function checkCli() {

  17. if (php_sapi_name() !== 'cli') {

  18. exit(output('服务只能运行在 cli sapi 模式下'));

  19. }

  20. }


  21. protected static function checkExtension() {

  22. if (!extension_loaded('swoole')) {

  23. exit(output('请安装 swoole 扩展'));

  24. }

  25. }


  26. protected static function showUsageUI() {

  27. global $argc;

  28. if ($argc <= 1 || $argc >3) {

  29. echo PHP_EOL;

  30. echo "----------------------------------------".PHP_EOL;

  31. echo "| Swoole |".PHP_EOL;

  32. echo "|--------------------------------------|".PHP_EOL;

  33. echo '| USAGE: php index.php commond |'.PHP_EOL;

  34. echo '|--------------------------------------|'.PHP_EOL;

  35. echo '| 1. start 以debug模式开启服务 |'.PHP_EOL;

  36. echo '| 2. start -d 以daemon模式开启服务 |'.PHP_EOL;

  37. echo '| 3. status 查看服务状态 |'.PHP_EOL;

  38. echo '| 4. reload 热加载 |'.PHP_EOL;

  39. echo '| 5. stop 关闭服务 |'.PHP_EOL;

  40. echo "----------------------------------------".PHP_EOL;

  41. echo PHP_EOL;

  42. exit;

  43. }

  44. }


  45. protected static function parseCommand() {

  46. global $argv;

  47. $command = $argv[1];

  48. $option = isset( $argv[2] ) ? $argv[2] : '' ;

  49. switch ($command) {

  50. case 'start':

  51. if ($option === '-d') { //以daemon形式启动

  52. get_config(['set@daemonize' => true]);

  53. }

  54. self::workerStart();

  55. break;

  56. case 'status':

  57. self::workerStatus();

  58. break;

  59. case 'reload':

  60. self::workerReload();

  61. break;

  62. case 'stop':

  63. self::workerStop();

  64. break;

  65. default:

  66. echo "Bad Command.".PHP_EOL;

  67. }

  68. }


  69. protected static function workerStart() {

  70. $config = get_config();


  71. self::$serv = new swoole_websocket_server($config['ip'], $config['websocket_port']);

  72. self::$serv->set($config['set']);

  73. self::$serv->on('Start', function ($serv) use ($config) {

  74. $start = new OnStart();

  75. $start::run($serv, $config);

  76. });


  77. self::$serv->on('ManagerStart', function ($serv) use ($config) {

  78. $manager_start = new OnManagerStart();

  79. $manager_start::run($serv, $config);

  80. });


  81. self::$serv->on('WorkerStart', function ($serv, $worker_id) use ($config) {

  82. $worker_start = new OnWorkerStart();

  83. $worker_start::run($serv, $worker_id, $config);

  84. });


  85. //TCP

  86. $tcp = self::$serv->listen($config['ip'], $config['tcp_port'], SWOOLE_SOCK_TCP);

  87. $tcp->set($config['tcp_set']);

  88. $tcp->on('Receive', function ($serv, $fd, $reactor_id, $data) {

  89. $receive = new OnReceive();

  90. $receive::run($serv, $fd, $reactor_id, $data);

  91. });


  92. //UDP

  93. $udp = self::$serv->listen($config['ip'], $config['udp_port'], SWOOLE_SOCK_UDP);

  94. $udp->set($config['udp_set']);

  95. $udp->on('Packet', function ($serv, $data, $client_info) {

  96. $packet = new OnPacket();

  97. $packet::run($serv, $data, $client_info);

  98. });


  99. self::$serv->on('Task', function ($serv, $task_id, $src_worker_id, $data) use ($config) {

  100. $task = new OnTask();

  101. $dataArr = json_decode($data, true);

  102. switch ($dataArr['server']) {

  103. case "tcp":

  104. $task::tcp_task_run($serv, $task_id, $src_worker_id, $data);

  105. break;

  106. case "ws":

  107. $task::ws_task_run($serv, $task_id, $src_worker_id, $data);

  108. break;

  109. }

  110. });


  111. self::$serv->on('Open', function ($serv, $request) {

  112. echo output("onOpen: handshake success with fd={$request->fd}");

  113. });


  114. self::$serv->on('Message', function ($serv, $frame) {

  115. $message = new OnMessage();

  116. $message::run($serv, $frame);

  117. });


  118. self::$serv->on('Request', function ($request, $response) {

  119. $req = new OnRequest();

  120. $req::run($request, $response);

  121. });


  122. self::$serv->on('Finish', function ($serv, $task_id, $data) {

  123. $finish = new OnFinish();

  124. $finish::run($serv, $task_id, $data);

  125. });


  126. self::$serv->on('Close', function ($serv, $fd, $reactor_id){

  127. try {

  128. echo output('客户端关闭');

  129. } catch(Exception $e) {

  130. }

  131. });


  132. self::$serv->on('Shutdown', function ($serv) {

  133. echo output("服务关闭");

  134. });


  135. self::showProcessUI();


  136. self::$serv->start();

  137. }


  138. protected static function workerStatus() {

  139. $config = get_config();


  140. if (!file_exists($config['master_pid_file']) ||

  141. !file_exists($config['manager_pid_file']) ||

  142. !file_exists($config['worker_pid_file']) ) {

  143. echo output("暂无启动的服务");

  144. return false;

  145. }


  146. self::showProcessUI($config);


  147. $masterPidString = trim(@file_get_contents($config['master_pid_file']));

  148. $masterPidArr = explode( '-', $masterPidString);


  149. echo str_pad("Master", 18, ' ', STR_PAD_BOTH ).

  150. str_pad($config['master_process_name'], 26, ' ', STR_PAD_BOTH ).

  151. str_pad($masterPidArr[0], 16, ' ', STR_PAD_BOTH ).

  152. str_pad($masterPidArr[1], 16, ' ', STR_PAD_BOTH ).

  153. str_pad($masterPidArr[2], 16, ' ', STR_PAD_BOTH ).PHP_EOL;


  154. $managerPidString = trim(@file_get_contents($config['manager_pid_file']));

  155. $managerPidArr = explode( '-', $managerPidString);


  156. echo str_pad("Manager", 20, ' ', STR_PAD_BOTH ).

  157. str_pad($config['manager_process_name'], 24, ' ', STR_PAD_BOTH ).

  158. str_pad($managerPidArr[0], 16, ' ', STR_PAD_BOTH ).

  159. str_pad($managerPidArr[1], 16, ' ', STR_PAD_BOTH ).

  160. str_pad($managerPidArr[2], 16, ' ', STR_PAD_BOTH ).PHP_EOL;



  161. $workerPidString = rtrim(@file_get_contents($config['worker_pid_file']), '|' );

  162. $workerPidArr = explode( '|', $workerPidString );

  163. if (isset($workerPidArr) && !empty($workerPidArr)) {

  164. foreach ($workerPidArr as $key => $val) {

  165. $v = explode( '-', $val);

  166. echo str_pad("Worker", 18, ' ', STR_PAD_BOTH ).

  167. str_pad($config['worker_process_name'], 26, ' ', STR_PAD_BOTH ).

  168. str_pad($v[0], 16, ' ', STR_PAD_BOTH ).

  169. str_pad($v[1], 16, ' ', STR_PAD_BOTH ).

  170. str_pad($v[2], 16, ' ', STR_PAD_BOTH ).PHP_EOL;

  171. }

  172. }


  173. $taskPidString = rtrim(@file_get_contents($config['task_pid_file']), '|' );

  174. $taskPidArr = explode( '|', $taskPidString );

  175. if (isset($taskPidArr) && !empty($taskPidArr)) {

  176. foreach ($taskPidArr as $key => $val) {

  177. $v = explode( '-', $val);

  178. echo str_pad("Task", 18, ' ', STR_PAD_BOTH ).

  179. str_pad($config['task_process_name'], 24, ' ', STR_PAD_BOTH ).

  180. str_pad($v[0], 20, ' ', STR_PAD_BOTH ).

  181. str_pad($v[1], 12, ' ', STR_PAD_BOTH ).

  182. str_pad($v[2], 20, ' ', STR_PAD_BOTH ).PHP_EOL;

  183. }

  184. }

  185. }


  186. protected static function workerReload() {

  187. $config = get_config();


  188. if (!file_exists($config['master_pid_file'])) {

  189. echo output("暂无启动的服务");

  190. return false;

  191. }


  192. $masterPidString = trim(file_get_contents($config['master_pid_file']));

  193. $masterPidArr = explode( '-', $masterPidString);


  194. if (!swoole_process::kill($masterPidArr[0], 0)) {

  195. echo output("PID:{$masterPidArr[0]} 不存在");

  196. return false;

  197. }


  198. swoole_process::kill($masterPidArr[0], SIGUSR1);


  199. @unlink($config['worker_pid_file']);

  200. @unlink($config['task_pid_file']);


  201. echo output("热加载成功");

  202. return true;

  203. }


  204. protected static function workerStop() {

  205. $config = get_config();


  206. if (!file_exists($config['master_pid_file'])) {

  207. echo output("暂无启动的服务");

  208. return false;

  209. }


  210. $masterPidString = trim(file_get_contents($config['master_pid_file']));

  211. $masterPidArr = explode( '-', $masterPidString);


  212. if (!swoole_process::kill($masterPidArr[0], 0)) {

  213. echo output("PID:{$masterPidArr[0]} 不存在");

  214. return false;

  215. }


  216. swoole_process::kill($masterPidArr[0]);


  217. $time = time();

  218. while (true) {

  219. usleep(2000);

  220. if (!swoole_process::kill($masterPidArr[0], 0)) {

  221. unlink($config['master_pid_file']);

  222. unlink($config['manager_pid_file']);

  223. unlink($config['worker_pid_file']);

  224. unlink($config['task_pid_file']);

  225. echo output("服务关闭成功");

  226. break;

  227. } else {

  228. if (time() - $time > 5) {

  229. echo output("服务关闭失败,请重试");

  230. break;

  231. }

  232. }

  233. }

  234. return true;

  235. }


  236. protected static function showProcessUI() {

  237. $config = get_config();

  238. if ($config['set']['daemonize'] == true) {

  239. return false;

  240. }

  241. echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;

  242. echo "|" . str_pad("启动/关闭", 92, ' ', STR_PAD_BOTH) . "|" . PHP_EOL;

  243. echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;


  244. echo str_pad("Start success.", 50, ' ', STR_PAD_BOTH) .

  245. str_pad("php index.php stop", 50, ' ', STR_PAD_BOTH) . PHP_EOL;


  246. echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;

  247. echo "|" . str_pad("版本信息", 92, ' ', STR_PAD_BOTH) . "|" . PHP_EOL;

  248. echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;


  249. echo str_pad("Swoole Version:" . SWOOLE_VERSION, 50, ' ', STR_PAD_BOTH) .

  250. str_pad("PHP Version:" . PHP_VERSION, 50, ' ', STR_PAD_BOTH) . PHP_EOL;


  251. echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;

  252. echo "|" . str_pad("IP 信息", 90, ' ', STR_PAD_BOTH) . "|" . PHP_EOL;

  253. echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;


  254. echo str_pad("IP:" . $config['ip'], 50, ' ', STR_PAD_BOTH) .

  255. str_pad("PORT:" . $config['websocket_port'], 50, ' ', STR_PAD_BOTH) . PHP_EOL;


  256. echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;

  257. echo "|" . str_pad("进程信息", 92, ' ', STR_PAD_BOTH) . "|" . PHP_EOL;

  258. echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;


  259. echo str_pad("Swoole进程", 20, ' ', STR_PAD_BOTH) .

  260. str_pad('进程别名', 30, ' ', STR_PAD_BOTH) .

  261. str_pad('进程ID', 18, ' ', STR_PAD_BOTH) .

  262. str_pad('父进程ID', 18, ' ', STR_PAD_BOTH) .

  263. str_pad('用户', 18, ' ', STR_PAD_BOTH) . PHP_EOL;

  264. }


  265. protected static function signalHandler() {

  266. //TODO 未完成

  267. //swoole_process::signal(SIGINT, function ($signal) {

  268. // echo $signal;

  269. // return;

  270. //});

  271. }

  272. }

小结

耗费了 3 个晚上的时间,终于完成了一个初版,比较初级,希望可以给入门的同学一个参考吧。

当然我自己也会继续完善它,后期的一些新知识点会集成到这里面,做成自己迭代的小项目。

初版比较糙,不喜勿喷。

后期会新增:

  • RPC

  • Coroutine - MySQL

  • Coroutine - Redis

  • Process

  • ...

需要源码的,加我微信。(菜单-> 加我微信-> 扫我)

推荐阅读

本文欢迎转发,转发请注明作者和出处,谢谢!图片

34480Swoole 整合成一个小框架

这个人很懒,什么都没留下

文章评论