Swoole 整合成一个小框架
概述
这是关于 Swoole 学习的第六篇文章:Swoole 整合成一个小框架。
- 第五篇:Swoole 多协议 多端口 的应用
- 第四篇:Swoole HTTP 的应用
- 第三篇:Swoole WebSocket 的应用
- 第二篇:Swoole Task 的应用
- 第一篇:Swoole Timer 的应用
写了关于 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 这是文件名称,大家叫什么都可以。
目录结构如下:
├─ controller
│ ├── ...
├─ client
│ ├─ websocket
│ ├── ...
│ ├─ tcp
│ ├── ...
├─ server
│ ├─ config
│ ├── config.php
│ ├─ core
│ ├── Common.php
│ ├── Core.php
│ ├── HandlerException.php
│ ├─ log -- 需要 读/写 权限
│ ├── ...
├─ 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)。
<?php
if (!defined('SERVER_PATH')) exit("No Access");
class Core
{
private static $serv;
public function __construct() {
set_error_handler(['HandlerException', 'appError']);
register_shutdown_function(['HandlerException', 'fatalError']);
}
public static function run() {
static::checkCli();
static::checkExtension();
static::showUsageUI();
static::parseCommand();
}
protected static function checkCli() {
if (php_sapi_name() !== 'cli') {
exit(output('服务只能运行在 cli sapi 模式下'));
}
}
protected static function checkExtension() {
if (!extension_loaded('swoole')) {
exit(output('请安装 swoole 扩展'));
}
}
protected static function showUsageUI() {
global $argc;
if ($argc <= 1 || $argc >3) {
echo PHP_EOL;
echo "----------------------------------------".PHP_EOL;
echo "| Swoole |".PHP_EOL;
echo "|--------------------------------------|".PHP_EOL;
echo '| USAGE: php index.php commond |'.PHP_EOL;
echo '|--------------------------------------|'.PHP_EOL;
echo '| 1. start 以debug模式开启服务 |'.PHP_EOL;
echo '| 2. start -d 以daemon模式开启服务 |'.PHP_EOL;
echo '| 3. status 查看服务状态 |'.PHP_EOL;
echo '| 4. reload 热加载 |'.PHP_EOL;
echo '| 5. stop 关闭服务 |'.PHP_EOL;
echo "----------------------------------------".PHP_EOL;
echo PHP_EOL;
exit;
}
}
protected static function parseCommand() {
global $argv;
$command = $argv[1];
$option = isset( $argv[2] ) ? $argv[2] : '' ;
switch ($command) {
case 'start':
if ($option === '-d') { //以daemon形式启动
get_config(['set@daemonize' => true]);
}
self::workerStart();
break;
case 'status':
self::workerStatus();
break;
case 'reload':
self::workerReload();
break;
case 'stop':
self::workerStop();
break;
default:
echo "Bad Command.".PHP_EOL;
}
}
protected static function workerStart() {
$config = get_config();
self::$serv = new swoole_websocket_server($config['ip'], $config['websocket_port']);
self::$serv->set($config['set']);
self::$serv->on('Start', function ($serv) use ($config) {
$start = new OnStart();
$start::run($serv, $config);
});
self::$serv->on('ManagerStart', function ($serv) use ($config) {
$manager_start = new OnManagerStart();
$manager_start::run($serv, $config);
});
self::$serv->on('WorkerStart', function ($serv, $worker_id) use ($config) {
$worker_start = new OnWorkerStart();
$worker_start::run($serv, $worker_id, $config);
});
//TCP
$tcp = self::$serv->listen($config['ip'], $config['tcp_port'], SWOOLE_SOCK_TCP);
$tcp->set($config['tcp_set']);
$tcp->on('Receive', function ($serv, $fd, $reactor_id, $data) {
$receive = new OnReceive();
$receive::run($serv, $fd, $reactor_id, $data);
});
//UDP
$udp = self::$serv->listen($config['ip'], $config['udp_port'], SWOOLE_SOCK_UDP);
$udp->set($config['udp_set']);
$udp->on('Packet', function ($serv, $data, $client_info) {
$packet = new OnPacket();
$packet::run($serv, $data, $client_info);
});
self::$serv->on('Task', function ($serv, $task_id, $src_worker_id, $data) use ($config) {
$task = new OnTask();
$dataArr = json_decode($data, true);
switch ($dataArr['server']) {
case "tcp":
$task::tcp_task_run($serv, $task_id, $src_worker_id, $data);
break;
case "ws":
$task::ws_task_run($serv, $task_id, $src_worker_id, $data);
break;
}
});
self::$serv->on('Open', function ($serv, $request) {
echo output("onOpen: handshake success with fd={$request->fd}");
});
self::$serv->on('Message', function ($serv, $frame) {
$message = new OnMessage();
$message::run($serv, $frame);
});
self::$serv->on('Request', function ($request, $response) {
$req = new OnRequest();
$req::run($request, $response);
});
self::$serv->on('Finish', function ($serv, $task_id, $data) {
$finish = new OnFinish();
$finish::run($serv, $task_id, $data);
});
self::$serv->on('Close', function ($serv, $fd, $reactor_id){
try {
echo output('客户端关闭');
} catch(Exception $e) {
}
});
self::$serv->on('Shutdown', function ($serv) {
echo output("服务关闭");
});
self::showProcessUI();
self::$serv->start();
}
protected static function workerStatus() {
$config = get_config();
if (!file_exists($config['master_pid_file']) ||
!file_exists($config['manager_pid_file']) ||
!file_exists($config['worker_pid_file']) ) {
echo output("暂无启动的服务");
return false;
}
self::showProcessUI($config);
$masterPidString = trim(@file_get_contents($config['master_pid_file']));
$masterPidArr = explode( '-', $masterPidString);
echo str_pad("Master", 18, ' ', STR_PAD_BOTH ).
str_pad($config['master_process_name'], 26, ' ', STR_PAD_BOTH ).
str_pad($masterPidArr[0], 16, ' ', STR_PAD_BOTH ).
str_pad($masterPidArr[1], 16, ' ', STR_PAD_BOTH ).
str_pad($masterPidArr[2], 16, ' ', STR_PAD_BOTH ).PHP_EOL;
$managerPidString = trim(@file_get_contents($config['manager_pid_file']));
$managerPidArr = explode( '-', $managerPidString);
echo str_pad("Manager", 20, ' ', STR_PAD_BOTH ).
str_pad($config['manager_process_name'], 24, ' ', STR_PAD_BOTH ).
str_pad($managerPidArr[0], 16, ' ', STR_PAD_BOTH ).
str_pad($managerPidArr[1], 16, ' ', STR_PAD_BOTH ).
str_pad($managerPidArr[2], 16, ' ', STR_PAD_BOTH ).PHP_EOL;
$workerPidString = rtrim(@file_get_contents($config['worker_pid_file']), '|' );
$workerPidArr = explode( '|', $workerPidString );
if (isset($workerPidArr) && !empty($workerPidArr)) {
foreach ($workerPidArr as $key => $val) {
$v = explode( '-', $val);
echo str_pad("Worker", 18, ' ', STR_PAD_BOTH ).
str_pad($config['worker_process_name'], 26, ' ', STR_PAD_BOTH ).
str_pad($v[0], 16, ' ', STR_PAD_BOTH ).
str_pad($v[1], 16, ' ', STR_PAD_BOTH ).
str_pad($v[2], 16, ' ', STR_PAD_BOTH ).PHP_EOL;
}
}
$taskPidString = rtrim(@file_get_contents($config['task_pid_file']), '|' );
$taskPidArr = explode( '|', $taskPidString );
if (isset($taskPidArr) && !empty($taskPidArr)) {
foreach ($taskPidArr as $key => $val) {
$v = explode( '-', $val);
echo str_pad("Task", 18, ' ', STR_PAD_BOTH ).
str_pad($config['task_process_name'], 24, ' ', STR_PAD_BOTH ).
str_pad($v[0], 20, ' ', STR_PAD_BOTH ).
str_pad($v[1], 12, ' ', STR_PAD_BOTH ).
str_pad($v[2], 20, ' ', STR_PAD_BOTH ).PHP_EOL;
}
}
}
protected static function workerReload() {
$config = get_config();
if (!file_exists($config['master_pid_file'])) {
echo output("暂无启动的服务");
return false;
}
$masterPidString = trim(file_get_contents($config['master_pid_file']));
$masterPidArr = explode( '-', $masterPidString);
if (!swoole_process::kill($masterPidArr[0], 0)) {
echo output("PID:{$masterPidArr[0]} 不存在");
return false;
}
swoole_process::kill($masterPidArr[0], SIGUSR1);
@unlink($config['worker_pid_file']);
@unlink($config['task_pid_file']);
echo output("热加载成功");
return true;
}
protected static function workerStop() {
$config = get_config();
if (!file_exists($config['master_pid_file'])) {
echo output("暂无启动的服务");
return false;
}
$masterPidString = trim(file_get_contents($config['master_pid_file']));
$masterPidArr = explode( '-', $masterPidString);
if (!swoole_process::kill($masterPidArr[0], 0)) {
echo output("PID:{$masterPidArr[0]} 不存在");
return false;
}
swoole_process::kill($masterPidArr[0]);
$time = time();
while (true) {
usleep(2000);
if (!swoole_process::kill($masterPidArr[0], 0)) {
unlink($config['master_pid_file']);
unlink($config['manager_pid_file']);
unlink($config['worker_pid_file']);
unlink($config['task_pid_file']);
echo output("服务关闭成功");
break;
} else {
if (time() - $time > 5) {
echo output("服务关闭失败,请重试");
break;
}
}
}
return true;
}
protected static function showProcessUI() {
$config = get_config();
if ($config['set']['daemonize'] == true) {
return false;
}
echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;
echo "|" . str_pad("启动/关闭", 92, ' ', STR_PAD_BOTH) . "|" . PHP_EOL;
echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;
echo str_pad("Start success.", 50, ' ', STR_PAD_BOTH) .
str_pad("php index.php stop", 50, ' ', STR_PAD_BOTH) . PHP_EOL;
echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;
echo "|" . str_pad("版本信息", 92, ' ', STR_PAD_BOTH) . "|" . PHP_EOL;
echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;
echo str_pad("Swoole Version:" . SWOOLE_VERSION, 50, ' ', STR_PAD_BOTH) .
str_pad("PHP Version:" . PHP_VERSION, 50, ' ', STR_PAD_BOTH) . PHP_EOL;
echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;
echo "|" . str_pad("IP 信息", 90, ' ', STR_PAD_BOTH) . "|" . PHP_EOL;
echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;
echo str_pad("IP:" . $config['ip'], 50, ' ', STR_PAD_BOTH) .
str_pad("PORT:" . $config['websocket_port'], 50, ' ', STR_PAD_BOTH) . PHP_EOL;
echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;
echo "|" . str_pad("进程信息", 92, ' ', STR_PAD_BOTH) . "|" . PHP_EOL;
echo str_pad("-", 90, '-', STR_PAD_BOTH) . PHP_EOL;
echo str_pad("Swoole进程", 20, ' ', STR_PAD_BOTH) .
str_pad('进程别名', 30, ' ', STR_PAD_BOTH) .
str_pad('进程ID', 18, ' ', STR_PAD_BOTH) .
str_pad('父进程ID', 18, ' ', STR_PAD_BOTH) .
str_pad('用户', 18, ' ', STR_PAD_BOTH) . PHP_EOL;
}
protected static function signalHandler() {
//TODO 未完成
//swoole_process::signal(SIGINT, function ($signal) {
// echo $signal;
// return;
//});
}
}
小结
耗费了 3 个晚上的时间,终于完成了一个初版,比较初级,希望可以给入门的同学一个参考吧。
当然我自己也会继续完善它,后期的一些新知识点会集成到这里面,做成自己迭代的小项目。
初版比较糙,不喜勿喷。
后期会新增:
- RPC
- Coroutine - MySQL
- Coroutine - Redis
- Process
- ...
需要源码的,加我微信吧。(菜单-> 加我微信-> 扫我)
本文欢迎转发,转发请注明作者和出处,谢谢!
Swoole 整合成一个小框架的更多相关文章
- 分析一个类似于jquery的小框架
在网上下了一个类似于jQuery的小框架,分析源码,看看怎么写框架. 选择器Select //用沙箱闭包其整个代码,只有itcast和I暴漏在全局作用域 (function( window , und ...
- ios 常用的小框架
在ios开发中,一些请求 kvc 下拉列表 图片请求等等自己手写代码的话非常麻烦,还容易出现一系列的问题,现在整理了一些常用的一些小框架. 其中MJExtension 和 MJRefresh 这两个 ...
- SQL SERVER 中 实现主表1行记录,子表多行记录 整合成一条虚拟列
表中有这样的记录,简单的主子表,现要想通过left join 语句把两表关联起来 select * from tbl_diary_reback a left join tbl_diary_reback ...
- Lua学习2 Lua小框架的搭建
看了上一篇Lua环境搭建具体http://blog.csdn.net/liuwumiyuhuiping/article/details/9196435 为了方便学习. 具体新开始搭建一下学习的小框架. ...
- D3D 练习小框架
自己练习D3D 程序搭的小框架,记录在这里,将来看到好回顾自己独自摸索的苦逼样子. #pragma once #pragma comment(lib,"d3d9.lib") #pr ...
- 实现AOP功能的封装与配置的小框架
内容 java基础巩固笔记 - 实现AOP功能的封装与配置的小框架 设计(目录): XXX = java.util.ArrayList中 代码 Advice接口 MyAdvice类 BeanFacto ...
- 利用jdbc简单封装一个小框架(类似DBUtils)
利用jdbc写的一个类似DBUtils的框架 package com.jdbc.orm.dbutils; import java.io.IOException; import java.io.Inpu ...
- 01 整合IDEA+Maven+SSM框架的高并发的商品秒杀项目之业务分析与DAO层
作者:nnngu 项目源代码:https://github.com/nnngu/nguSeckill 这是一个整合IDEA+Maven+SSM框架的高并发的商品秒杀项目.我们将分为以下几篇文章来进行详 ...
- 使用Python开发的POC多线程批量执行小框架
因为代码量非常少,所以就叫“小框架”吧. 接口非常简陋,但是好处是适配POC脚本的时候很灵活,兼容性高,不需要任何研究成本. 简单来说,你按照自己的想法和习惯开发一个POC验证程序,它只要做到三点,即 ...
随机推荐
- Spark- 优化后的 shuffle 操作原理剖析
在spark新版本中,引入了 consolidation 机制,也就是说提出了ShuffleGroup的概念.一个 ShuffleMapTask 将数据写入 ResultTask 数量的本地文本,这个 ...
- 浅谈WebService开发(一)
一.什么是WebService: 简单通俗来说,就是企业之间.网站之间通过Internet来访问并使用在线服务,一些数据,由于安全性问题,不能提供数据库给其他单位使用,这时候可以使 用WebSer ...
- log4net调试
public delegate void LogReceivedEventHandler(object source, LogReceivedEventArgs e); public sealed c ...
- C#实现读写文本文件中的数据
[1]首先我们定义一段假数据,这里以一个string为例字 static void Main(string[] args) { string data = &quo ...
- LOJ2722 「NOI2018」情报中心
「NOI2018」情报中心 题目描述 C 国和D 国近年来战火纷飞. 最近,C 国成功地渗透进入了D 国的一个城市.这个城市可以抽象成一张有$n$ 个节点,节点之间由$n - 1$ 条双向的边连接的无 ...
- [JSOI 2015] 最大公约数
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=4488 [算法] 不妨首先枚举左端点 注意到对于任意一个正整数n , 其质因子个数是l ...
- Eclipse或MyEclipse中给第三方jar包添加源码步骤
0.目的 向web项目中添加mybatis源码. 1.项目结构如下 将mybatis的jar包添加到工程中 2.解压下载的mybatis压缩包(下载地址 https://github.com/myba ...
- Maven 排除依赖jar包
当我们引入第三方jar包的时候,难免会引入传递性依赖,有些时候这是好事,然而有些时候我们不需要其中的一些传递性依赖 比如我们不想引入传递性依赖commons-logging,我们可以使用exclusi ...
- HDOJ1677(铺砖问题)
Nested Dolls Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Tota ...
- MongoDB分析工具之一:explain()语句分析工具
explain(),语句分析工具 MongoDB 3.0之后,explain的返回与使用方法与之前版本有了很大的变化,介于3.0之后的优秀特色和我们目前所使用给的是3.0.7版本,本文仅针对Mongo ...