概述

这是关于 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 这是文件名称,大家叫什么都可以。

目录结构如下:

├─ 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 整合成一个小框架的更多相关文章

  1. 分析一个类似于jquery的小框架

    在网上下了一个类似于jQuery的小框架,分析源码,看看怎么写框架. 选择器Select //用沙箱闭包其整个代码,只有itcast和I暴漏在全局作用域 (function( window , und ...

  2. ios 常用的小框架

    在ios开发中,一些请求 kvc 下拉列表  图片请求等等自己手写代码的话非常麻烦,还容易出现一系列的问题,现在整理了一些常用的一些小框架. 其中MJExtension 和 MJRefresh 这两个 ...

  3. SQL SERVER 中 实现主表1行记录,子表多行记录 整合成一条虚拟列

    表中有这样的记录,简单的主子表,现要想通过left join 语句把两表关联起来 select * from tbl_diary_reback a left join tbl_diary_reback ...

  4. Lua学习2 Lua小框架的搭建

    看了上一篇Lua环境搭建具体http://blog.csdn.net/liuwumiyuhuiping/article/details/9196435 为了方便学习. 具体新开始搭建一下学习的小框架. ...

  5. D3D 练习小框架

    自己练习D3D 程序搭的小框架,记录在这里,将来看到好回顾自己独自摸索的苦逼样子. #pragma once #pragma comment(lib,"d3d9.lib") #pr ...

  6. 实现AOP功能的封装与配置的小框架

    内容 java基础巩固笔记 - 实现AOP功能的封装与配置的小框架 设计(目录): XXX = java.util.ArrayList中 代码 Advice接口 MyAdvice类 BeanFacto ...

  7. 利用jdbc简单封装一个小框架(类似DBUtils)

    利用jdbc写的一个类似DBUtils的框架 package com.jdbc.orm.dbutils; import java.io.IOException; import java.io.Inpu ...

  8. 01 整合IDEA+Maven+SSM框架的高并发的商品秒杀项目之业务分析与DAO层

    作者:nnngu 项目源代码:https://github.com/nnngu/nguSeckill 这是一个整合IDEA+Maven+SSM框架的高并发的商品秒杀项目.我们将分为以下几篇文章来进行详 ...

  9. 使用Python开发的POC多线程批量执行小框架

    因为代码量非常少,所以就叫“小框架”吧. 接口非常简陋,但是好处是适配POC脚本的时候很灵活,兼容性高,不需要任何研究成本. 简单来说,你按照自己的想法和习惯开发一个POC验证程序,它只要做到三点,即 ...

随机推荐

  1. 超快的maven setting文件

    <?xml version="1.0"?> <settings> <localRepository>/home/yizhen/.m2/repos ...

  2. 大话设计模式--适配器模式 Adapter -- C++实现实例

    1.适配器模式: 将一个类的接口转换为客户希望的另一个接口,使得原来由于接口不能一起工作的那些类一起工作. 适配器模式一般用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况. 适配器模式分 ...

  3. Spark- RDD持久化

    官方原文: RDD Persistence One of the most important capabilities in Spark is persisting (or caching) a d ...

  4. Spark- 流量日志分析

    日志生成 package zx.Utils import java.io.{File, FileWriter} import java.util.Calendar import org.apache. ...

  5. CheckStyle:unable to parse configuration stream - Element type "message" must be declared

    版本在1.3以上,包括1.3: <!DOCTYPE module PUBLIC          "-//Puppy Crawl//DTD Check Configuration 1. ...

  6. 在接口中的静态方法来获取model的实例对象

    直接先上代码: 接口代码 abstract class BaseModel { /** * @var null */ protected static $_instances = Array(); / ...

  7. 什么是 Sass 其与SCSS区别是什么?

    Sass 官网上是这样描述 Sass 的: Sass 是一门高于 CSS 的元语言,它能用来清晰地.结构化地描述文件样式,有着比普通 CSS 更加强大的功能. Sass 能够提供更简洁.更优雅的语法, ...

  8. BEC listen and translation exercise 42

    These were built for the workers towards the end of the eighteenth century, and they are still furni ...

  9. PyQt5布局管理(1)

    Qt布局管理按简单分可分为绝对位置布局和布局管理器布局 一.绝对位置布局: 组件不放在布局管理器中,通过函数setGeometry(x,y,width,height)来设定组件相对其父窗口的位置.其中 ...

  10. CCUserDefault 存档

    void HelloWorld::testCCUserDefault() { CCSize size = CCDirector::sharedDirector()->getWinSize(); ...