基于swoole协程轻量级mvc框架,启动服务的进程占用很少的内存,一般在10M以内,这个框架上手很容易,框架简单实用。 可以针对不同的服务端封装不同的服务端入口,目前封装好了基于swoole的websocket服务端、基于swoole的redis服务端。 其中redis服务端拿来解决php-fpm需要并发执行sql等业务,可以使用swoole的协程并发处理mysql、redis、curl、等异步IO业务。
PHP 7.1 以上 swoole 4.5.x 以上
1.git clone 本仓库地址
2.composer update
1.一键协程化,主要是针对网络IO协程,例如mysql、redis、CURL、file_get_contents等
当然用作其他并发执行的代码都可以。
使用这个方法开启 \Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
这就意味着mysql、redis、curl、file_get_contents等网络方法开启了协程的支持、框架底层默认是开启了上述客户端的协程支持,
相当于你在使用mysql,redis、curl、file_get_contents等网络客户端操作的时候可以得到协程并发的支持。
2.框架目录结构说明:
.
├── application
│ ├── common // 公共模块
│ │ ├── controller // 控制器目录
│ │ │ └── CommonController.php // 控制器
│ │ ├── logic // 逻辑文件目录
│ │ ├── model // 模型文件目录
│ │ │ ├── UserModel.php // 模型文件
│ │ └── validate // 验证器目录
│ │ └── TestValidate.php // 验证器
│ ├── demo // swoole redis server模块
│ │ ├── controller
│ │ │ └── DemoController.php
│ │ ├── logic
│ │ │ └── DemoLogic.php
│ │ ├──model
│ │ │ └── TestModel.php
│ │ ├── functions.php // 当前模块的函数定义文件
│ │ ├── config.php // 当前模块的配置文件(主要配置swoole服务相关的配置)
│ │ ├── Event.php // 接收swoole的事件,在事件里面处理路由等业务
│ ├── websocket // websocket模块 主要用户聊天类的业务
│ │ ├── controller
│ │ │ ├── LoginController.php
│ │ │ └── MessageController.php
│ │ ├── functions.php
│ │ ├── logic
│ │ │ ├── LoginLogic.php
│ │ │ └── MessageLogic.php
│ │ └── model
│ │ │ └── TestModel.php
│ │ ├── database.php // 当前模块的数据库配置文件,优先加载模块下面的database.php
│ │ ├── Event.php
│ │ ├── config.php
│ ├── functions.php // 公共函数库
├── composer.json // composer配置文件
├── composer.lock
├── config // 公共的配置文件目录
│ ├── app.php // 自定义的一些配置信息
│ ├── database.php // 公共数据库配置文件,如果模块下面没有database.php文件则会读取这个文件
│ ├── define.php // 全局的一些常量定义文件
│ └── elasticSearch.php // 自定义的配置文件 这样来取配置config('fileName', 'key') $config = config('elasticSearch', 'elastic_search');
├── orangeswoole // 框架目录 开发一般不用关心这个目录
│ ├── controller
│ │ └── Controller.php
│ ├── define.php
│ ├── event
│ │ ├── EventBase.php
│ │ ├── EventRedisServer.php
│ │ └── EventWebSocketServer.php
│ ├── helper.php
│ ├── include.php
│ ├── logic
│ │ └── Logic.php
│ ├── model
│ │ ├── Model.php
│ │ └── Pdo.php
│ ├── server
│ │ ├── Commands.php
│ │ ├── Run.php
│ │ ├── ServerBase.php
│ │ ├── SwooleRedisServer.php
│ │ └── SwooleWebSocketServer.php
│ └── Validate.php // thinkPHP的验证器
├── README.en.md
├── README.md
├── runtime // 项目运行目录,存放日志,swoole进程id等
│ ├── cert // 如果websocket等这类业务需要开启wss业务,可以加入证书文件,你可以放其他目录
│ │ ├── www.xxx.com-fullchain.pem
│ │ └── www.xxx.com-privkey.pem
│ ├── log
│ │ ├── DemoSwooleServer.log
│ │ └── WebSocketSwooleServer.log
│ ├── DemoSwooleServer.pid // Demo模块运行时的进程id文件
│ └── WebSocketSwooleServer.pid // websocket模块运行时的进程id文件
├── swooleserver // swoole服务类启动入口文件、在框架中可以建立多个模块,然后不同的模块建立自己单独的服务启动入口
│ ├── DemoSwooleServer.php // swoole 模拟 redis 服务启动入口文件
│ └── WebSocketSwooleServer.php // websocket模块的服务启动入口文件,swoole服务启动类 像这样启动 当前目录下 php WebSocketSwooleServer.php start(调试模式启动)
├── tools // 公共的工具类目录
│ ├── Common.php
│ ├── ElasticSearchClient.php
│ └── Redis.php
└── websocket.html // 这里是一个websocket客户端例子、和项目中的websocket模块是一对儿的
3.swoole的redis服务端介绍(这个模块主要是拿来处理php-fpm中想要并发处理sql、curl等业务)
A.cd 到swoolserver目录
B.php DemoSwooleServer.php start|restart|stop|status 其中 start -d(不加参数-d的话调试模式启动) 以守护进程模式启动
C.启动之后,php-fpm的业务代码中(客户端)像这样调用
$redis = new \Redis;
// 127.0.0.1 redis服务监听的ip地址 9501 端口号
$redis->connect('127.0.0.1', 9501);
$params = [
'sql1' => 'select count(*) AS count from user',
'sql2' => 'select count(*) AS count from order',
'sql3' => 'select count(*) AS count from table'
];
// 传递给控制器的参数
$params = json_encode($params);
// demo/Demo/querySql 这就是你想访问的swoole redis服务端中的、模块、控制器、方法
$result = $redis->hget("demo/Demo/querySql", $params);
$result = json_decode($result, true);
var_dump($result);
D.然后你在swoole的项目中根据这个路径demo/Demo/querySql(模块、控制器、方法)中写具体想并发处理的业务例如并发处理多条sql等,看起来像这样:
public function querySql($data)
{
// 这里可以加一些参数验证
$waitGroup = new \Swoole\Coroutine\WaitGroup();
$sql1 = $data['sql1']; // select count(*) AS count from user
$sql2 = $data['sql2']; // select count(*) AS count from order
$sql3 = $data['sql3']; // select count(*) AS count from table
$returnData = [];
// 并发处理sql1
go(function () use ($waitGroup, $sql1, &$returnData) {
$userModel = new $UserModel;
$result = $userModel->query($sql1);
$returnData['sql1'] = $result;
// 协程执行完成
$waitGroup->done();
});
// 并发处理sql2
go(function () use ($waitGroup, $sql2, &$returnData) {
$userModel = new $UserModel;
$result = $userModel->query($sql2);
$returnData['sql2'] = $result;
// 协程执行完成
$waitGroup->done();
});
// 并发处理sql3
go(function () use ($waitGroup, $sql3, &$returnData) {
$userModel = new $UserModel;
$result = $userModel->query($sql3);
$returnData['sql3'] = $result;
// 协程执行完成
$waitGroup->done();
});
// 等待所有的协程执行完毕
$waitGroup->wait();
return returnData(1000, '操作成功', $returnData);
}
其中sql1和sql2、sql3会得到并发处理,来提高php-fpm业务中的处理速度
4.模块下面的配置文件,以demo模块为例,路径是application/demo/config.php
<?php
/**
* 应用设置
* 使用说明
* cd 到 swooleserver目录
* 命令行 php DemoSwooleServer.php start|restart|stop|status 其中 start -d 以守护进程模式启动
*/
return [
// 开启/关闭调试模式 true false
'debug' => true,
// swoole官方配置,如需其他官方配置请自行修改
'swoole' => [
// 监听所有ip
'listen' => '0.0.0.0',
// swoole启动端口
'port' => '9501',
// 加入此参数后,执行php server.php将转入后台作为守护进程运行 1守护 0非守护
'daemonize' => 0,
// 通过此参数来调节主进程内事件处理线程的数量,以充分利用多核。默认会启用 CPU 核数相同的数量。
// Reactor 线程是可以利用多核,如:机器有 128 核,那么底层会启动 128 线程。
// 每个线程能都会维持一个 EventLoop。线程之间是无锁的,指令可以被 128 核 CPU 并行执行。
// 考虑到操作系统调度存在一定程度的性能损失,可以设置为 CPU 核数 * 2,以便最大化利用 CPU 的每一个核。
// reactor_num 建议设置为 CPU 核数的 1-4 倍
// reactor_num 必须小于或等于 worker_num 如果设置的 reactor_num 大于 worker_num,会自动调整使 reactor_num 等于 worker_num
//'reactor_num' => 1,
// 设置启动的Worker进程数量。Swoole采用固定Worker进程的模式。全异步非阻塞服务器 worker_num配置为CPU核数的1-4倍即可。
'worker_num' => 4,
// Listen队列长度,
//'backlog' => 256,
// 此选项表示每隔多久轮循一次,单位为秒。如 heartbeat_check_interval => 60,
// 表示每 60 秒,遍历所有连接,如果该连接在 120 秒内(heartbeat_idle_time 未设置时默认为 interval 的两倍),
// 没有向服务器发送任何数据,此连接将被强制关闭。若未配置,则不会启用心跳,该配置默认关闭
'heartbeat_check_interval' => 60,
// TCP连接的最大闲置时间,单位s , 如果某fd最后一次发包距离现在的时间超过heartbeat_idle_time会把这个连接关闭。
// 如果只设置了 heartbeat_idle_time 未设置 heartbeat_check_interval 底层将不会创建心跳检测线程
// PHP 代码中可以调用 heartbeat 方法手工处理超时的连接
'heartbeat_idle_time ' => 300,
// 日志文件
'log_file' => RUNTIME_PATH.'/log/WebSocketSwooleServer.log',
// 主进程id
'pid_file' => RUNTIME_PATH.'/WebSocketSwooleServer.pid',
// 证书cert wss 开启说明,可以只用nginx代理转发实现 wss且可以去掉端口号,也可以单独使用此项功能开启wss但是无法去掉端口号
//'ssl_cert_file' => RUNTIME_PATH.'/cert/xxx.pem',
// 证书key
//'ssl_key_file' => RUNTIME_PATH.'/cert/xxx.pem',
// 进程用户
//'user' => 'root',
// 进程用户组
//'group' => 'root',
// 如开启异步安全重启, 需要在workerExit释放连接池资源
//'reload_async' => true,
// 设置 Worker 进程收到停止服务通知后最大等待时间【默认值:3】
//'max_wait_time' => 3,
// 设置 worker 进程的最大任务数。【默认值:0 即不会退出进程
// 一个 worker 进程在处理完超过此数值的任务后将自动退出,进程退出后会释放所有内存和资源
//'max_request' => 1,
// 数据包分发策略。【默认值:2】
// 模式值 模式 作用
// 1 轮循模式收到会轮循分配给每一个 Worker进程
// 2 固定模式根据连接的文件描述符分配 Worker。这样可以保证同一个连接发来的数据只会被同一个 Worker 处理
// 3 抢占模式主进程会根据 Worker 的忙闲状态选择投递,只会投递给处于闲置状态的 Worker
// 4 IP分配根据客户端 IP 进行取模 hash,分配给一个固定的 Worker 进程。
// 可以保证同一个来源 IP 的连接数据总会被分配到同一个 Worker 进程。算法为 ip2long(ClientIP) % worker_num
// 5 UID分配需要用户代码中调用 Server->bind() 将一个连接绑定 1 个 uid。然后底层根据 UID 的值分配到不同的 Worker 进程。
// 算法为 UID % worker_num,如果需要使用字符串作为 UID,可以使用 crc32(UID_STRING)
// 7 stream模式空闲的Worker会accept连接,并接受Reactor的新请求
//'dispatch_mode' => 1,
],
// 下面是框架自定义配置
// 是否开启ssl true false 通过nginx转发了,这里不用开启ssl
'is_ssl' => false,
// swoole 运行的2种模式 SWOOLE_BASE SWOOLE_PROCESS
'swoole_mode' => SWOOLE_PROCESS,
// 接收swoole服务的事件类,这个需要设置成当前模块下面Event事件类的路径
'swoole_event' => '\app\demo\Event',
];
5.模块下面的数据库配置文件,以websocket模块为例,路径是application/websocket/database.php
<?php
/**
* default是默认的数据库配置信息,可以配置其他的数据库配置信息
* mysql数据库配置
* 优先会加载模块下面的database.php文件
*/
return [
'default' => [ // 默认数据库配置信息
'host' => 'host', // 服务器地址
'port' => 3306, // 端口
'user' => 'user', // 用户名
'password' => 'pass', // 密码
'charset' => 'utf8', // 编码
'database' => 'dbname', // 数据库名
'prefix' => '', // 表前缀
],
'other_db_config' => [ // 其他配置数据库配置信息
'host' => 'host', // 服务器地址
'port' => 3306, // 端口
'user' => 'user', // 用户名
'password' => 'pass', // 密码
'charset' => 'utf8', // 编码
'database' => 'dbname', // 数据库名
'prefix' => '', // 表前缀
],
];
模块里面这样来切换数据库配置信息
<?php
namespace app\common\model;
class UserModel extends CommonModel
{
/**
* 这里是定义当前模型所使用的数据库连接
*/
public $connect = 'other_db_config';
/**
* 这里是定义当前模型的表
*/
public $table = 'user';
}
模型的使用
$userModel = new UserModel();
$result = $userModel->query('select * from table');
目前数据库mysql操作类是原生的,后面尽量用hyperf的数据库ORM(用这个是因为它基于swoole协程优化过),这样比较友好一点
或者自己在基于原生的sql封装一层数据库ORM
6.websocket业务的介绍,H5代码看起来就像这样:
websocket服务端就像swoole的redis服务端一样,进入 swooleservder目录
php WebSocketSwooleServer.php start (-d 守护模式启动)
<html>
<head>
<script>
var socket;
if ("WebSocket" in window) {
// token 用户信息验证
// 连接事件 主要处理登录等业务 通过token参数验证登录
var ws = new WebSocket("ws://127.0.0.1:9501?token=abcadsfdasfasdfkljaefklj");
socket = ws;
ws.onopen = function() {
alert("连接成功!");
};
// 具体的业务处理
ws.onmessage = function(evt) {
var received_msg = evt.data;
var received = JSON.parse(received_msg);
var msg = received.msg;
// action 区别业务类型、这里是发送消息的业务,根据自定义的action参数返回的receive_message值进行业务处理
// 这里是发送消息业务
if (received.action == 'receive_message') {
document.getElementById("showMes").value+=received.data.message+"\n";
} else {
document.getElementById("showMes").value+=msg+"\n";
}
};
ws.onclose = function() {
alert("断开了连接");
};
} else {
alert("浏览器不支持WebSocket");
}
// 发送消息
function sendMes(){
var message=document.getElementById("mes").value;
var to = document.getElementById("name").value;
message = '{"to":"'+to+'","message":"'+message+'"}';
// 用户端和后端交互以固定的json格式交互
// {"route":"websocket/Message/message","params":"自定义业务内容"};
// route key是路由参数,websocket(模块)/Message(控制器)/message(方法)
var data = '{"route":"websocket/Message/message","params":'+message+'}';
socket.send(data);
}
// 保持心跳
window.setInterval(function() {
var data = 'ping';
socket.send(data);
},5000)
</script>
</head>
<body>
<textarea rows="3" cols="30" id="showMes" style="width:300px;height:500px;"></textarea>
<br/>
<label>发送给:</label>
<!--发送给的内容填写和服务端连接的fd,一般情况下,启动后台服务之后,第一个连接的fd是1,第二是2,以此类推
实际业务中,指定用户的id,用户的唯一标识就行了,在服务端进行fd和用户的唯一标识user_id等进行绑定
-->
<input type="text" id="name"/>
<br/>
<label>消息内容:</label>
<input type="text" id="mes"/>
<button onclick="sendMes();">发送</button>
</body>
</html>
swoole后端代码看起就像这样:
文件路径application/websocket/Event.php
/**
* 客户端连接到服务器事件
* 在连接的时候处理登录相关业务
* @author zk
* @param $server swoole server 对象
* @param $request 用户请求对象
* @return bool|void
*/
public function onOpen($server, $request)
{
$loginController = new LoginController();
$startTime = microtime(true);
$result = $loginController->login($request);
$endTime = microtime(true);
$result['run_time'] = $endTime - $startTime;
return send($this->server, $request->fd, $result);
}
通过application/websocket/controller/LoginController.php的login方法转发到
application/websocket/logic/LoginLogic.php的login方法上
<?php
namespace app\websocket\logic;
use orangeswoole\logic\Logic;
// 处理登录的逻辑文件
class LoginLogic extends Logic
{
/**
* 登录
* @param $request
* @return mixed
*/
public function login($request)
{
$token = $request->get['token']; // 参数过滤等
if (empty($token)) {
return returnMessage(1001, '参数错误');
}
// 登陆成功之后通过redis绑定fd到token
//$prefix = CONFIG['redis']['prefix'];
// $connectionKey = $prefix.'connection_'.$token;
//$this->redis->set($connectionKey, $request->fd, 3600);
$returnData = [
'user_info' => ['name' => '张三', 'user_id' => 1]
];
return returnMessage(1000, '登录连接成功', 'login', $returnData);
}
}
处理message业务的代码:根据前端传入的route:websocket/Message/message会路由到这里来
<?php
namespace app\websocket\logic;
use orangeswoole\logic\Logic;
class MessageLogic extends Logic
{
/**
* 发送消息
* @author zk
* @param $data
* @return mixed
*/
public function message($data)
{
$sendData = [
'message' => $data['message']
];
send($this->server, $data['to'], returnMessage(1000, '接收到信息', 'receive_message', $sendData));
return returnMessage(1000, '操作成功', 'send_message');
}
}
把项目中的websocket.html文件用浏览器打开2个,然后相互就可以发送消息了,注意发送的id一般情况填写1和2就行了
7.负载均衡
Nginx 反向代理
Nginx 是一个高性能的 HTTP 和反向代理服务器,代码完全用 C 实现,基于它的高性能以及诸多优点,我们可以把它设置为swoole服务的前置服务器,实现负载均衡或 HTTPS 前置服务器等。
配置 Http 代理
# 至少需要一个swoole服务节点,多个配置多行
下面的配置需要在nginx.conf的http{}节点下
upstream swoole_http {
# swoole HTTP Server 的 IP 及 端口
server 127.0.0.1:9501;
server 127.0.0.1:9502;
}
server {
# 监听端口
listen 80;
# 绑定的域名,填写您的域名
server_name www.xxx.com;
location / {
# 将客户端的 Host 和 IP 信息一并转发到对应节点
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 转发Cookie,设置 SameSite
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
# 执行代理访问真实服务器
proxy_pass http://swoole_http;
}
}
配置 Websocket 代理
# 至少需要一个 swoole-websocket 节点,多个配置多行
upstream swoole_websocket {
# 设置负载均衡模式为 IP Hash 算法模式,这样不同的客户端每次请求都会与同一节点进行交互
ip_hash;
# swoole WebSocket Server 的 IP 及 端口
server 127.0.0.1:9503;
server 127.0.0.1:9504;
}
server {
listen 80;
server_name websocket.xxx.com;
location / {
# WebSocket Header
proxy_http_version 1.1;
proxy_set_header Upgrade websocket;
proxy_set_header Connection "Upgrade";
# 将客户端的 Host 和 IP 信息一并转发到对应节点
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
# 客户端与服务端无交互 60s 后自动断开连接,请根据实际业务场景设置
proxy_read_timeout 60s ;
# 执行代理访问真实服务器
proxy_pass http://swoole_websocket;
}
}
配置 tcp 代理
在nginx.conf中增加如下配置信息:
#tcp代理
stream {
include /www/server/nginx/conf/tcp/*.conf;
}
在/www/server/nginx/conf/tcp/目录下新建文件swoole_tcp.conf,内容如下:
#负载配置
upstream swoole_tcp {
least_conn;
server 127.0.0.1:9505;
server 127.0.0.1:9506;
}
#参数自行调整
server {
listen 9507;
proxy_pass swoole_tcp;
proxy_timeout 600s;
error_log /www/xxx/runtime/log/nginx_swoole_error.log;
}
8.框架的基本常量
文件在orangeswoole/define.php
<?php
//项目目录
define('PROJECT_PATH', dirname(__DIR__));
//swoole服务目录
define('SWOOLE_SERVER_PATH', PROJECT_PATH.'/swooleserver');
//composer扩展目录
define('VENDOR_PATH', PROJECT_PATH.'/vendor');
//应用目录
define('APPLICATION_PATH', PROJECT_PATH.'/application');
//运行日志,缓存等目录
define('RUNTIME_PATH', PROJECT_PATH.'/runtime');
//框架目录
define('ORANGE_SWOOLE_PATH', PROJECT_PATH.'/orangeswoole');
//config目录
define('CONFIG_PATH', PROJECT_PATH.'/config');
还有个模块里面的常量
定义在orangeswoole/server/Run.php文件中
// 模块名称
define('MODULE', $module);
// 模块路径
define('MODULE_PATH', APPLICATION_PATH.'/'.$module);
// 当前模块的配置文件
define('CONFIG', $config);
9.配置文件的获取
该方法在orangeswoole/helper.php
/**
* 获取config目录下的配置文件信息
* 获取config/app.php的error_reporting配置
* $errorReporting = config('app', 'error_reporting'); // $errorReporting = E_ALL
* @param $file
* @param $key
* @return mixed
*/
function config($file, $key)
{
$config = include CONFIG_PATH.'/'.$file.'.php';
return $config[$key];
}
/**
* 获取模块下面的配置文件
* 获取application/websocket/config.php的debug配置
* $debug = moduleConfig('config', 'debug');// $debug = true
* @param $file
* @param $key
* @return mixed
*/
function moduleConfig($file, $key)
{
$config = include APPLICATION_PATH.'/'.MODULE.'/'.$file.'.php';
return $config[$key];
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。