小伙伴们,铁钩钓鱼了。。
为什么要搞个钩子呢?我来说一个场景吧,稍大点的项目IM和通知服务是不会放在一起的,应用场景不同,放在一起可能会造成资源浪费。所以大部分项目的IM和通知服务是分别放在不同的服务器上部署的。WEBSOCKET间的通信有可能是会被隔离的。怎么办呢,于是就有这么一个简单的处理办法。那就是,把各个WEBSOCKET的FD存到NOSQL数据库里,比如REDIS。然后根据不同的用户来获取它所在的服,然后就能实现跨服务通信了。
首先,我要先创建文件/app/common/Redis.php,这个类很简单,首先它是单例模式,REDIS的操作方法,全部用魔术方法来实现调用,代码如下
<?php
namespace app\common ;
class Redis
{
private static $instance;
private function __construct ()
{
try{
self::$instance = new \Redis();
self::$instance->pconnect (config('redis.host'),config ('redis.port'));
if(config ('redis.passwd','')!=''){
self::$_instance->auth(config ('redis.passwd'));
}
self::$instance->select(config ('redis.db'));
\Piz\Log::get_instance()->write ("INFO","REDIS","已连接",config('redis.host').":".config ('redis.port'));
}catch (\RedisException $e){
self::$instance = NULL ;
\Piz\Log::get_instance()->write ("INFO","REDIS",$e->getMessage ());
}
}
public static function get_instance(){
if(is_null (self::$instance)){
new self();
}
return self::$instance;
}
public function __call($method ,$args=NULL){
$this->handle->$method(...$args);
}
}
缺一个redis的配置文件,创建它 config/redis.php ,配置很简单
<?php
return [
'host' => '127.0.0.1',
'port' => 6379,
'passwd' => '',
'db' => 0,
];
我们这里只是简单的实现一个REDIS的连接和操作,至于REDIS集群、HASH一致性等算法那就不是本篇考虑的内容了,小伙伴们可以自行百度。。
REDIS类的开发工作就完成了。接下来就同本篇的重点 钩子。
创建文件frame/Lib/Hook.php,老样子,单例模式,再来一个listen方法,有一个前提,有的时候在某一个位置可能需要共存多个钩子。怎么办呢?咱可以把它放到配置文件里。
完成Hook.php的代码前,先创建钩子的配置文件,在这个本置文件里,我只创建了三个点位,并且提前把要配置的信息也填了进去,代码如下
<?php
return [
//Server::onStart
'start' => [
[\app\hook\FD::class,'start'],
],
//Server::onOpen
'open' => [
[\app\hook\FD::class,'open'],
],
//Server::onClose
'close' => [
[\app\hook\FD::class,'close'],
],
];
小伙伴们能看明白吧。说白了就是我目前只需要在Server.php里的对应这三个回调方法里埋点。
接下来就写Hook.php。。代码如下
<?php
namespace Piz;
class Hook
{
private static $instance;
private static $config ;
private function __construct ()
{
}
public static function get_instance(){
if(is_null (self::$instance)){
self::$instance = new self();
self::$config = Config::get_instance ()->get("hook");
}
return self::$instance;
}
public function listen($hook , ...$args){
$hooks = isset(self::$config[$hook]) ? self::$config[$hook] : [] ;
while($hooks){
list($class,$func) = array_shift ($hooks);
try{
$class::get_instance()->$func(...$args);
}catch (\Exception $e){
Log::get_instance ()->write ('ERROR',$e->getMessage ());
}
}
}
}
代码是同样的简单。这次我在catch下埋了记录异常错误的日志点。这个写完了。我们要把它放到Server.php对应的点位了。修改代码如下
public function onStart($server){
Log::get_instance()->write('INFO',"启动成功","{$this->config['ip']}:{$this->config['port']}");
Hook::get_instance ()->listen('start',$server);
}
public function onOpen( $server,$request){
Log::get_instance()->write('DEBUG',"FD:{$request->fd}","握手成功");
Hook::get_instance ()->listen('open',$server,$request->fd);
}
public function onClose($server,$fd){
Log::get_instance()->write('DEBUG',"FD:{$fd}","关闭连接");
Hook::get_instance ()->listen('close',$server,$fd);
}
Hook.php写完了,点也埋好了。接下来就是写对应的钩子业务逻辑了
首先,为了满足本篇开头说的记录连接的FD,必须有一个握手成功时要存储的方法,然后在用户断开后必须有一个销毁FD的方法,最重要的一个情况是,如果服务端断点,已存在的FD是没法继续保持的,怎么办呢?在启动时,把它们全部清掉。
接下来,创建文件app/hook/FD.php ,必须是单例械式,代码如下
<?php
/**
* 管理FD
*/
namespace app\hook ;
class FD
{
private static $instance;
private function __construct ()
{
}
public static function get_instance(){
if(is_null (self::$instance)){
self::$instance = new self();
}
return self::$instance;
}
public function start($server){
\app\common\Redis::get_instance ()->del("FD");
\Piz\Log::get_instance()->write ("INFO","Hook","重置FD表");
}
public function open($server,$fd){
\app\common\Redis::get_instance ()->sAdd("FD",$fd);
\Piz\Log::get_instance()->write ("INFO","Hook","写入REDIS集合","FD:{$fd}");
}
public function close($server,$fd){
\app\common\Redis::get_instance ()->sRem("FD",$fd);
\Piz\Log::get_instance()->write ("INFO","Hook","移出REDIS集合","FD:{$fd}");
}
public function __call($method ,$args=NULL){
$this->$method(...$args);
}
}
启动start.php 看效果
好玩了。。。
今天就到这了,周末如果有时间的话,就把Model简单说一下,其实Model很简单,小伙伴们可以自己整合一下你再习惯使用ORM或其它ORM。我也只是整合了一个别人的简单ORM。所以,这个可有可有。。不重要。
下周的主要工作就优化一下性通,优化一下启动程序,埋点监控之类的,基础上也就这些了。
以上工作完成后,我们就用这个小框架做几个小项目,让大家能更深入的理解它的应用方法。