您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么用swoole + js + redis實現簡易聊天室”,在日常操作中,相信很多人在怎么用swoole + js + redis實現簡易聊天室問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么用swoole + js + redis實現簡易聊天室”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
公司需要用到在線聊天功能,先寫了個demo出來展示效果。
主要用到了swoole的websocket,task和redis的string,hash,set,zet等。
聊天室分為10個頻道,可以切換頻道,頻道計數等。
目前還沒做聊天內容的加密。
按需求,聊天內容可能會每個頻道保留最近一百條,聊天加密的話考慮aes,也有可能不加。后續看情況了。
目前還沒做異常處理。回頭繼續完善之后可能會再進行更新。
先上代碼吧,有疑問可以留言交流。這里是html代碼 可以自己引入jq地址。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>WebSocketTestPage</title> <script src="/jquery.js" type="text/javascript"></script> </head> <body> <input type="text" id="button"> <input type="button" onclick="senMsg()" value="發送"> <hr> <textarea type="text" id="msgBox" ></textarea> <script> var wsUrl = "ws://39.100.xx.xx:8003/"; var webSocket = new WebSocket(wsUrl); //實例對象onOpen屬性 webSocket.onopen = function(evt){ changeMsg('聊天室已接入'); webSocket.send('{"gid":2041461173415,"uid":46676437104256,"msg_type":0}'); console.log("conected-swoole-success"); webSocket.send("已接入"); }; webSocket.onmessage = function(evt){ changeMsg( '收到消息:' + evt.data); console.log("ws-return-data:" + evt.data); }; webSocket.onclose = function(evt){ changeMsg( '已關閉'); console.log("close-ws-client"); }; webSocket.onerror = function(evt,e){ changeMsg( '已關閉' + evt.data + e); console.log("error--" + evt.data); }; function senMsg(){ val = $('#button').val(); webSocket.send(val); } function changeMsg(msg){ $('#msgBox').append(msg + "\n"); } </script> </body> </html>
服務端代碼
<?php const HOST = "0.0.0.0"; const PORT = 8003; const WORKER = 16; const TASK = 10; const S_NAME = 'server_'; const CHANNEL_MAX_SIZE = 10; const ROOM_MAX_SIZE = 1000; const CNL = 'zset_channel_num_list';//zset key 計數用 const CR = 'set_chat_room_';//頻道編號 zset的member 以及set存儲連接id const GR = 'set_guild_room_';//公會聊天頻道 const CF = 'hash_chat_fd_';//聊天用戶信息 hash const CL = 'list_chat_info_';//聊天信息 list const GCL = 'list_guild_chat_info_';//公會聊天信息 list const CL_MAX_SIZE = 20;//每個頻道保留聊天信息上限 const SM = 'string_sys_msg_';//系統消息 string class Ws { public $ws = null; private $redis = null; private $in_param = [ 0 => ['uid','gid'], 1 => ['new_room','old_room'], 2 => ['level','data','room_id','uid','name','head'], 4 => ['level','data','gid','uid','name','head'], ]; public function __construct() { if(!isset($this->redis)){ $this->redis = $this->_getRedis(); $this->redis->select(10); $this->redis->flushDB(); } $this->_resetChatNum(); if(!isset($this->ws)){ $this->ws = new swoole_websocket_server( HOST, PORT); $this->ws->set( [ 'worker_num' => WORKER, 'task_worker_num' => TASK, ] ); $this->ws->on("open", [$this,"onOpen"]); $this->ws->on("message", [$this,"onMessage"]); $this->ws->on("close", [$this,"onClose"]); $this->ws->on("task", [$this,"onTask"]); $this->ws->on("finish", [$this,"onFinish"]); $this->ws->start(); } } public function onOpen($ws, $request) { $msg = array( 'msg_type' => 0, ); $this->_pushSysNotice($request->fd); $ws->push($request->fd, json_encode($msg)); } private function _pushOldMsg($fd, $channel, $guild = false){ $m = $this->redis->lRange(CL . $channel,0,CL_MAX_SIZE); $msg = []; foreach($m as $v){ $msg[] = json_decode($v,true); } if(!empty($msg)) $this->ws->task(['fds'=>[$fd],'msg'=>$msg]); $this->redis->lTrim(CL . $channel,0,CL_MAX_SIZE); if($guild){ $g_m = $this->redis->lRange(GCL . $guild,0,CL_MAX_SIZE); $g_msg = []; foreach($g_m as $v){ $g_msg[] = json_decode($v,true); } if(!empty($g_msg)) $this->ws->task(['fds'=>[$fd],'msg'=>$g_msg]); $this->redis->lTrim(GCL . $guild,0,CL_MAX_SIZE); } } private function _pushSysNotice($fd){ $this->redis->select(0); $keys = $this->redis->keys(SM . '*'); $msg = array( 'msg_type' => 3, ); if(!empty($keys)){ foreach($keys as $v){ $s_msg = $this->redis->get($v); $msg['data'] = $s_msg; $this->ws->push($fd, json_encode($msg)); } } $this->redis->select(10); } public function onMessage($ws, $frame) { $data = json_decode($frame->data,true); $ret = []; switch($data['msg_type']){ case 0://初始化 if($this->_checkParam($this->in_param[0], $data)){ $ret = $this->_setConnectInfo($frame->fd, $data); if($data['gid'] > 0) $this->_pushOldMsg($frame->fd,$ret['room_id'],$data['gid']); }else{ $ret = ['msg_type'=>'no param']; } break; case 1://切換頻道 if($this->_checkParam($this->in_param[1], $data)){ $ret = $this->_changeChannel($frame->fd, $data); }else{ $ret = ['msg_type'=>'no param']; } break; case 2://聊天 if($this->_checkParam($this->in_param[2], $data)){ $ret = $this->_pushChatInfo($frame->fd, $data); }else{ $ret = ['msg_type'=>'no param']; } break; case 3: break; case 4://公會聊天 if($this->_checkParam($this->in_param[4], $data)){ $ret = $this->_pushChatInfo($frame->fd, $data,true); }else{ $ret = ['msg_type'=>'no param']; } break; default: $ws->push($frame->fd, "error"); break; } echo "fd: {$frame->fd} Message: {$frame->data} \n"; if(!empty($ret)){ $ws->push($frame->fd, json_encode($ret)); } } public function onClose($ws, $fd) { $this->_delChatInfo($fd, true); } public function onTask($ws, $taskId, $workerId, $data) { foreach($data['fds'] as $v){ $this->ws->push($v,json_encode($data['msg'])); } return $taskId; } public function onFinish($ws, $taskId, $data) { echo "task-{$taskId} is end\n"; } //初始化頻道計數器 private function _resetChatNum(){ for($i = 1; $i <= CHANNEL_MAX_SIZE; $i++){ $this->redis->zAdd(CNL, 0, CR . $i); } } //獲取人數最少頻道 private function _getSuggestRoomId(){ $chat_list = $this->redis->zRange(CNL,0,0); $no = substr($chat_list[0],14); if(empty($no) || (int)$no < 1 || (int)$no > CHANNEL_MAX_SIZE){ $no = mt_rand(1,CHANNEL_MAX_SIZE); } return (int)$no; } //連接時設置推薦頻道 private function _setConnectInfo($fd, $data){ $channel = $this->_getSuggestRoomId(); $this->_setChannelInfo($fd,$channel); $this->redis->hSet(CF.$fd, 'uid',$data['uid']); $this->redis->hSet(CF.$fd, 'gid',$data['gid']); if(!empty($data['gid'])){ $this->redis->sAdd(GR.$data['gid'], $fd); } return array( 'msg_type' => 1, 'data' => $channel, 'room_id' => $channel, 'code' => 0, 'fd' => $fd, ); } //設置頻道相關數據 private function _setChannelInfo($fd, $channel){ //存入fd $this->redis->sAdd(CR . $channel,$fd); //變更計數器 $this->redis->zIncrBy(CNL, 1, CR . $channel); //存入用戶頻道信息 $this->redis->hSet(CF.$fd, 'channel',(int)$channel); } //校驗字段 private function _checkParam($param, $data){ foreach($param as $v){ if(!isset($data[$v]))return false; } return true; } //切換頻道 private function _changeChannel($fd, $data){ $this->_delChatInfo($fd); $this->_setChannelInfo($fd,$data['new_room']); $this->_pushOldMsg($fd,$data['new_room']); return array( 'msg_type' => 1, 'data' => $data['new_room'], 'code' => 1, ); } //關閉連接時清除相關內容 private function _delChatInfo($fd, $del = false){ $channel = $this->redis->hget(CF.$fd,'channel'); if(empty($channel)) return true; $this->redis->sRem(CR .$channel, $fd); if($del){ $this->redis->del(CF.$fd); } $this->redis->zIncrBy(CNL, -1, CR . $channel); } //推送聊天信息 private function _pushChatInfo($fd, $data, $guild = false){ if(!$guild){ $user = $this->redis->hGetAll(CF.$fd); $fds = $this->redis->sMembers(CR . $user['channel']); $this->redis->lPush(CL . $user['channel'],json_encode($data)); $type = 2; }else{ $fds = $this->redis->sMembers(GR . $data['gid']); $this->redis->lPush(GCL . $data['gid'],json_encode($data)); $type = 4; } $msg = array( 'msg_type' => $type, 'uid' => $data['uid'], 'name' => $data['name'], 'data' => $data['data'], 'level' => $data['level'], 'head' => $data['head'], 'code' => $type, ); $this->ws->task(['fds'=>$fds,'msg'=>$msg]); return []; } //初始化redis資源 private function _getRedis() { $redis = new Redis(); $redis->connect('127.0.0.1', 6379); return $redis; } } //啟動 new Ws();
請求示例
{"new_room":8,"old_room":1,"msg_type":1} //切換頻道 {"level":10,"data":"嗷嗷嗷啊","room_id":1,"msg_type":2,"uid":xxxx,"name":1028,"head":1}//聊天 {"gid":xxxx,"uid":xxxx,"msg_type":0} //初始化,其實這塊本來想寫道open里面但是這樣的話需要前端改動,就先這樣了。
到此,關于“怎么用swoole + js + redis實現簡易聊天室”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。