easyswoole 搭建消息推送服务05 - websocket应用

鲜于喜
2023-12-01

上一个Demo里,在启动时注册了支持websocket的组件、解析器,在到映射到的路由,实现了简单的通信,在开发中也是现学现卖,摸着石头过河,难度也是不小的,机会和挑战总是成对出现的,所以要加油加油再加油,边学习边总结,才能有更大的进步。

用以下几个主要功能点:

1 = HEART		  	   // 心跳
2 = LOGIN			   // 登录
3 = LOGOUT			   // 退出登录
4 = COMMENT		       // 评论
5 = PULL		       // 拉取
6 = NOTIFICATION	   // 通知
7 = READ			   // 已读
8 = RECEIVED           // 收到确认

心跳检测

先从tcp协议说起,tcp的连接需要三次握手和四次挥手,在反复链接的过程中

先从tcp协议说起,tcp创建一个连接有三次握手,而断开一个连接有四次挥手。不管是服务器还是客户端发起连接的关闭,都会完整的走完四次挥手的过程,这样,一切很完美,系统回收这个fd,应用层也可以通过onClose回调处理相关的事情.

fd是需要重点理解的一个知识点 fd学名是文件描述符,在unix的哲学就是一切皆文件中,这个fd就是系统层暴露给业务层的用来表示一个五元组网络连接的标识。你可以简单的理解为一个索引,通过对这个fd的操作,系统层可以找到相应的连接而且进行的一系列操作,如发送数据到网瞳,进行连接关闭等等。

心跳检测存在的意义就是系统fd资源是有限的,当检测链接已断开时,在系统级别进行主动回收资源,判定时间的间隔,两次交互时间超过设置的时长,就主动断开链接。

配置文件dev.php

// 表示一个连接如果300秒内未向服务器发送任何数据,此连接将被强制关闭
'heartbeat_idle_time' => 180 , 
// 表示每60秒遍历一次
'heartbeat_check_interval' => 60 , 

断线重连最多三次,时间间隔为60s、120s、180s,是上次交互的时间 与检测时候的时间相差180s,就被被踢出。

iMessage

在解析器里做了一层分发,把请求数据按客户端类型进行区分,Pc、H5、Ios、Android,出于代码严谨程度的考虑,在所有的Controller上规定了一个interface接口,这个可以保证各端方法的统一,还可以增加在各类型上的个性化。

/**
 * Interface iMessage websocket 控制器继承接口
 *   1 = HEART		  	   // 心跳
 *   2 = LOGIN			   // 登录
 *   3 = LOGOUT			   // 退出登录
 *   4 = COMMENT		   // 评论
 *   5 = PULL		       // 拉取
 *   6 = NOTIFICATION	   // 通知
 *   7 = READ			   // 已读
 **/
 
namespace App\Library\IMessage;

interface iMessageController {

    function heart();

    function login();

    function logout();

    function comment();

    function pull();

    function notification();

    function read();
}

Contoller 代码展示:


namespace App\WebSocketController\V1;

class PcMessage extends Base implements iMessageController
{
	//code 略...
}

UID & fd 绑定关系

每次token验证成功,建立长链接时,用redis的 有序集合(sorted set)做存储类型,主要考虑的点有两个,有序集合中可以保证value是唯一的,这样就能保证一对一的uid和fd的对应关系,还有一点是这次开发还有广播的通知需求,这样可以更好的为广播服务。

/**
 * login 登录方法
 */
public function login()
{
    //验证参数代码略....
        
    $fd = $this->caller()->getClient()->getFd();

    $zaddRet = $redis->zAdd('PUSH_MSG_ZSET', $fd, $uid);
    $noce_ack = $this->getNoceAck();

    $pushMsg = [
        'noce_ack' => $noce_ack,
        'uid' => $uid,
        'to_uid' => 0,
        'is_sent' => 1,
        'msg_type' => 2,
        'client_type' => 1,
        'create_time' => time(),
        'update_time' => time()
    ];
    $listRet = $redis->lPush('PUSH_MSG_LISTS', json_encode($pushMsg));
    $model = PushMsgModel::create($pushMsg);
    $ret = $model->save();
    echo 'sql执行结果:'.$ret;

    

    //返回结果进行处理
    $result = [
        'uid' => $uid,
        'msg_type' => 2,
        'code' => 200,
        'msg'  => 'SUCCESS',
        'body' => '',
        'noce_ack' => $noce_ack,
    ];
    return $this->response()->setMessage(json_encode($result));

}

重点:这里需要注意的是对应关系保证唯一,一个uid不允许对应两个fd,会造成推送不准的问题 解决方案应该在关闭链接的时候清除对应的记录。

推送给指定用户

开始的时候,我犯了一个错误,以为响应的就是推送出去的数据,响应和推送是两个兴致不同的事情,WS的原理是一次握手,就不会断开了,可以进行实时的交互。响应和推送都是链接之后的反应。

实现步骤,Controller的具体代码

use EasySwoole\EasySwoole\ServerManager;

class PcMessage extends Base implements iMessageController
{

    public function pushMsg(){
        //实例化一个websocket server对象
        $server = ServerManager::getInstance()->getSwooleServer();
        //获取用户的fd
        $fd = $this->caller()->getClient()->getFd();
        $data = [
        	'uid' 		 => 17000223,
        	'noce_ack' => '453DF8E5-296A-3DF3-EB47-DD6425889D75'
        ];
        //推送消息
        $server->push($fd, json_encode($data));
		  //发送响应消息
        $this->response()->setMessage('this is heart');
    }

}

生成唯一标识的 noce_ack

在进行消息推送时,要记录通信的ack标识码,可以由服务端实现,也可以由客户端实现,这个项目由服务端实现,如:9857315A-CFFF-E845-F603-3068DEFFEAD5


/**
 * @return string 生成通信唯一的ack标识
 */
protected function getNoceAck(){
    mt_srand((double)microtime()*10000);//optional for php 4.2.0 and up.
    $charid = strtoupper(md5(uniqid(rand(), true)));
    $hyphen = chr(45);// "-"
    $noce_ack =// "{"
         substr($charid, 0, 8).$hyphen
        .substr($charid, 8, 4).$hyphen
        .substr($charid,12, 4).$hyphen
        .substr($charid,16, 4).$hyphen
        .substr($charid,20,12);
    return $noce_ack;
}

因为时间关系,就整理到这儿,onClose的时候需要回收集合里的对应关系的问题还没有找到,有小伙伴发现可以在底下留言,互相学习。

 类似资料: