之前發佈了第一個版本的羣聊實現,大家可以去看一下上篇文章 swoole實現多對多羣聊
這篇文章主要是在實現羣聊的基礎上加了日誌功能以及代碼的優化
老樣子,直接上代碼
PHP代碼
<?php
define('HOST', '0.0.0.0');
define('PORT', '19501');
define('WORKER_NUM', 2);
define('TASK_WORKER_NUM', 4);
class WebsocktDemo
{
public $ws = null;
public $redis = null;
public $pdo = null;
public function __construct()
{
$this->ws = new Swoole\WebSocket\Server(HOST, PORT);
$this->redis = $this->redis();
$this->redis->auth("***********");
//實例化數據庫鏈接
$this->pdo = new PDO(
"mysql:host=********;dbname=******;charset=utf8;",
"********",
"********",[\PDO::ATTR_CASE => \PDO::CASE_NATURAL]
);
//創建token找幾個小弟
$this->ws->set([
'worker_num' => WORKER_NUM,
'task_worker_num' => TASK_WORKER_NUM,
]);
//$this->ws->on("start", [$this, 'onStart']);
$this->ws->on("open", [$this, 'onOpen']);
$this->ws->on('message',[$this,'onMessage']);
$this->ws->on('close',[$this,'onClose']);
//token線程監聽
$this->ws->on("task", [$this, 'onTask']);
$this->ws->on('finish',[$this,'onFinish']);
$this->ws->start();
}
/**
* 獲取redis實例
* @return null|Redis
*/
protected function redis()
{
static $redis = null;
if (is_null($redis)) {
$redis = new \Redis();
$redis->connect("*********");
}
return $redis;
}
/**
* 監聽用戶鏈接事件,鏈接時需要帶用戶id與房間id參數,再把用戶存到房間域中
*/
public function onOpen($server, $request){
//加入房間域
$this->redis->hset($request->get['room'], $request->get['uid'], $request->fd);
//加入組集合
$this->redis->sadd('group', $request->get['room']);
//鏈接後,插入一條日誌
self::recordLog($request->fd, $request->get['uid'], $request->get['room'], "add");
}
/**
* $room 當前房間id
* $arr 組裝數據
*/
public function push_room($room, $arr)
{
//獲取在線用戶的fd
$push_arr = $this->redis->hvals($room);
//推送
foreach ($push_arr as $v) {
$this->ws->push($v, json_encode($arr));
}
}
/**
* 監聽接收事件的回調
*/
public function onMessage($server, $frame)
{
//在接收數據的時候進行推送,每次發送消息都要帶上標記信息(uid,room)
$data = json_decode($frame->data, true);
//組裝數據判斷類型
switch ($data['type']){
case "change"://發送消息
$arr['name'] = $data['name'];
$arr['content'] = $data['content'];
$arr['type'] = "change";
$arr['uid'] = $data['uid'];
$arr['room'] = $data['room'];
break;
}
//找小弟去推送消息到房間
$this->ws->task($data);
}
/**
* 監聽關閉事件的回調,這一步照搬,沒有做修改
*/
public function onClose($ser, $fd)
{
//退出並刪除多餘的分組,並更新日誌狀態
$this->ws->task(array("type"=>"clean","fd"=>$fd));
}
/**
* 線程小弟
*/
public function onTask($server, $task_id, $from_id, $data)
{
switch ($data['type']){
case "change":
self::push_room($data['room'], $data);
break;
case "clean":
self::cleanUser($data);
break;
}
return true;
}
/**
* 線程任務完成回調事件,這裏不需要回調什麼,但是必須要綁定這個事件
*/
public function onFinish($server, $task_id, $data)
{
echo "Task#$task_id finished";
}
//退出房間事件
public function cleanUser($data){
//退出並刪除多餘的分組fd
$group = $this->redis->sMembers('group');
foreach ($group as $v) {
$fangjian = $this->redis->hgetall($v);
if(empty($fangjian)) continue;
foreach ($fangjian as $k => $vv) {
if ($data['fd'] == $vv) {
$this->redis->hdel($v, $k);
self::recordLog($vv, $k, $v, "close");
//跳出兩層循環
break 2;
}
}
}
}
//房間記錄 fd-uid-room
private function recordLog($fd, $uid, $room_id, $type)
{
$sql = "";
switch ($type){
case "add":
$sql = "***********";
break;
case "close":
$sql = "**********";
break;
}
try {
$stmt = $this->pdo->prepare($sql);
$stmt->execute();
} catch (Exception $e) {
echo "recordLog error";
}
}
}
//最後實例化對象
new WebsocktDemo();
前端方面不需要改動,主要是優化了一下下代碼,加了一個統計在線時長的功能,找了個跑腿的小弟,這裏要注意一下,小弟不是越多越好,小弟也是要吃飯的,就算是閒着也會暫用你的內存,所以這裏只創建了4個,如果有什麼寫得不好的地方大家可以指導一下