用swoole擴展 做的h5版聊天室筆記
阿新 • • 發佈:2018-06-10
一個人 return hash php_eol d+ ear contain 消息 勿噴
聲明:該聊天室目前只有一對多,一對一的聊天功能,另外,因為沒有使用到mysql,所以還存在比較多的缺陷地方,但知道原理就差不多了,這裏主要分享下swoole簡易的聊天室制作思路。
開發環境:centos7、redis、swoole
首先看看效果圖
難點:這裏一對多比較容易實現,就是簡單的用h5裏websocket特新+swoole,但是一對一比較難實現,因為具體到信息發到哪個人,信息接收等問題
首先看前端js的代碼:
<script> $(‘.welcome‘).text(‘歡迎你進入聊天!‘); //最開始進入聊天室時提示歡迎 var wsurl = "ws://www.junheng.ink:9501"; var websocket = new WebSocket(wsurl); //申請一個WebSocket對象 websocket.onopen = function(evt){ //當websocket創建成功時,會觸發onopen事件 console.log("hello swoole"); } websocket.onmessage = function(evt){ //當客戶端收到服務端發來的消息時,會觸發onmessage事件 msg(evt.data); console.log("swoole-server-return-msg:" + evt.data ); } websocket.onclose = function(evt){ //當客戶端收到服務端發送的關閉連接的請求時,觸發onclose事件 console.log("bye swoole"); } websocket.onerror = function(evt,e){ //若中途出現失敗,會觸發onerror事件 console.log("error" + evt.data); } function speak_all(){ //這是用於發送消息給所有人的函數 let content = $("#chat_bottom_input").val(); data=JSON.stringify({content:content,type:"chat"}); console.log(data); websocket.send(data); } function speak_one(){ //這是用於發送消息給所有人的函數 let listener = $("#listener").text(); let content = $("#chat_bottom_input_ptp").val(); console.log(content); console.log(listener); data=JSON.stringify({content:content,listener:listener,type:"ptp_speak"}); console.log(data); websocket.send(data); } function msg(data){ //這是在onmessage函數裏面執行的(收到服務器返回信息時做對應操作) var data=JSON.parse(data); switch(data.type) { case 1: //1代表統計在線總人數 $(".total").html(data.usernum); break; case 2: //2代表在一對多情況下發信息、接信息兩種情況 console.log(data); var html =""; if(data.speaker==1){ html +=‘<div class="speak"><div class="my_picture"><span style="display:none">‘+data.user_id+‘</span><img class="pic" src="‘+data.headimg+‘"><div class="dialogue">‘+data.user+‘</div></div><span class="right_triangle"></span><div class="my_talk"><p class="talk_content">‘+data.content+‘</p></div><div class="my_nowtime">‘+data.nowtime+‘</div></div>‘; }else{ html +=‘<div class="speak"><div class="picture" onclick="ptp(this)"><span style="display:none">‘+data.user_id+‘</span><img class="pic" src="‘+data.headimg+‘"><div class="dialogue">‘+data.user+‘</div></div><span class="left_triangle"></span><div class="talk"><p class="talk_content">‘+data.content+‘</p></div><div class="nowtime">‘+data.nowtime+‘</div></div>‘; } $("#comments").append(html); $("#chat_bottom_input").val(""); var scheight=$("#chat_center")[0].scrollHeight; var ofheight=$("#chat_center").outerHeight(); $("#chat_center").scrollTop(scheight-ofheight); break; case 3: //3代表進入一對一時頭部填上對應是哪個人 $("#listener").html(data.listener); console.log(data); break; case 4: //4代表一對一時自己發送消息 console.log(data); var html =""; html +=‘<div class="speak"><div class="my_picture"><img class="pic" src="‘+data.speaer_headimg+‘"><div class="dialogue">我:‘+data.speaker+‘</div></div><span class="right_triangle"></span><div class="my_talk"><p class="talk_content">‘+data.content+‘</p></div><div class="my_nowtime">‘+data.nowtime+‘</div></div>‘; $(".new_msg").append(html); $("#chat_bottom_input_ptp").val(""); var scheight=$("#chat_center_ptp")[0].scrollHeight; var ofheight=$("#chat_center_ptp").outerHeight(); $("#chat_center_ptp").scrollTop(scheight-ofheight); break; case 5: //5代表一對一時別人發送消息 console.log(data); var html =""; html +=‘<div class="speak"><div class="picture""><img class="pic" src="‘+data.speaker_headimg+‘"><div class="dialogue">用戶:‘+data.speaker+‘</div></div><span class="left_triangle"></span><div class="talk"><p class="talk_content">‘+data.content+‘</p></div><div class="nowtime">‘+data.nowtime+‘</div></div>‘; $(".new_msg").append(html); var scheight=$("#chat_center_ptp")[0].scrollHeight; //這下面是把滾動框拉到最後意思 var ofheight=$("#chat_center_ptp").outerHeight(); $("#chat_center_ptp").scrollTop(scheight-ofheight); break; } } function ptp(event){ //這是點擊了某個人頭像,需要進行一對一聊天時觸發的函數 user_id=event.children[0].innerText; let data=JSON.stringify({user_id:user_id,type:"ptp"}); console.log(data); websocket.send(data); //這裏把被點擊人的id傳給後臺swoole,後面它根據這個id發一對一的信息 $("#container_room").css("display","none"); $("#container_ptp").css("display","flex"); } function back(){ //這是一對一時點擊了返回大廳按鈕觸發的函數 $(".new_msg").empty(); $("#container_room").css("display","flex"); $("#container_ptp").css("display","none"); let data=JSON.stringify({type:"ptp",want:"close"}); console.log(data); websocket.send(data); //這裏發送信息是為了關閉swoole的定時器(用來檢測是否有收到信息) var scheight=$("#chat_center")[0].scrollHeight; var ofheight=$("#chat_center").outerHeight(); $("#chat_center").scrollTop(scheight-ofheight); } </script>
然後再看看完整的後端代碼
date_default_timezone_set(‘PRC‘); class Ws{ /** * 這是定義靜態redis方法,方便後面使用 */ static public function myRedis(){ $redis = new Redis(); $redis->connect(‘127.0.0.1‘, 6379); return $redis; } public $ws = null; public function __construct(){ $this->ws = new swoole_websocket_server("0.0.0.0",9501); //創建websocket服務器 $this->ws->set([ ‘worker_num‘ => 4, //設置進程數 ‘task_worker_num‘ => 4 //設置task任務數 ]); $this->ws->on("start",[$this,‘onStart‘]); //這是最開始創建時觸發start事件 $this->ws->on("open",[$this,‘onOpen‘]); //這是前端的websocket連接時觸發事件 $this->ws->on("message",[$this,‘onMessage‘]); //這是接收到前端發來的信息時觸發事件 $this->ws->on("task",[$this,‘onTask‘]); //這是用來分發異步任務時觸發事件 $this->ws->on("finish",[$this,‘onFinish‘]); //這是task的回調函數 $this->ws->on("close",[$this,‘onClose‘]); //這是關閉時的觸發的事件 $this->ws->start(); //啟動websocket服務器 } /** * 為進程設置別名 */ public function onStart(){ swoole_set_process_name("swoole_chat"); //為進程id添加別名 } /** *監聽打開事件 */ public function onOpen($ws,$request){ //可以用定時器監聽是否有其他人的信息 // swoole_timer_tick(2000,function($time_id){ // echo "有消息嗎?2s-time_id:{$time_id}\n"; // }); $redis = self::myRedis(); //這裏用redis隨機存取出頭像 if(!$redis->sMembers(‘headimg‘)){ $redis->sAdd(‘headimg‘,"http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eokKDRjQuyqFIkpziaNalabDNicPibV3RJk0CmxdWSiaOUgYrqvucyGvHQ6oGHHwxYgYsC6LOcrYaGJ9A/132"); $redis->sAdd(‘headimg‘,"http://thirdwx.qlogo.cn/mmopen/vi_32/maKfSQsic5IOYjicXszArwLZOWbyW2zWj7TYzsDhgAbYgXnvehpjqhWmcYE91JyzFaJS2Dj6wMHdJvewRnVofeYw/132"); $redis->sAdd(‘headimg‘,"http://thirdwx.qlogo.cn/mmopen/vi_32/zr58J0258Ghbc8vtNhxZIyIUQwgOQnTn6NAnr1QYqe1CIm0uuBPkQwDA7cpY0JFvI6vhlwsDOEJJmkOwTDibLgQ/132"); $redis->sAdd(‘headimg‘,"http://thirdwx.qlogo.cn/mmopen/vi_32/u6KWfAsSuTJcvMvZOB5JmxZ9nnHZ4x4oofJ2COLvWcicD5F1fzibbYwyR4INb5CgS7nlnH9nlr2fpMgbuHfOEWBw/132"); $redis->sAdd(‘headimg‘,"http://thirdwx.qlogo.cn/mmopen/vi_32/zKKK0ZAicRAmPcU4FaOwvwOOVxSrP9icDBLHHhlDG7bJ4N7pePJEedVjThSZWTTlibLtTQsVSnzeJcQzkPTOfQL9Q/132"); $redis->sAdd(‘headimg‘,"http://thirdwx.qlogo.cn/mmopen/vi_32/IibMjuSiblgx57qfTvkKPePC8vvyjm0bBM852u0vS6GPvNEBLvMdKnzugibYJzVkOSuAXFlCRicicW1Cfpa0S8gIsibA/132"); $redis->sAdd(‘headimg‘,"http://thirdwx.qlogo.cn/mmopen/vi_32/ViaqjkbzPo33fqunp7vsJzzFSJeAiaQzCeaIALRnAPwHibibpFYm0RQ2VlDXasT5iapELic8McaZbC0cLEHTibtNXwsZA/132"); } //保存頭像和用戶id,使用戶和對應頭像關聯起來 $this->getheader($request->fd); //把連接的用戶全部放進chat集合 $redis->sAdd(‘chat‘,$request->fd); print_r("swoole服務端連接成功:{$request->fd}".PHP_EOL); //用task任務統計在線人數 $data=[ ‘type‘ => 1 //1代表用於統計在線人數 ]; $this->ws->task($data); //分發異步任務 } /** *監聽前端消息事件 */ public function onMessage($ws,$frame){ $redis = self::myRedis(); $content=$frame->data; $content=json_decode($content,true); switch($content[‘type‘]){ case ‘chat‘: //這是一對多時觸發 echo "ser-push-message:{$content[‘content‘]}\n"; //從hash結構取出對應用戶的頭像 $headimg=$redis->hGet(‘links‘,$frame->fd); //用task任務進行聊天 $data=[ ‘user_id‘ => $frame->fd, ‘user‘ => "用戶:".$frame->fd, ‘content‘ => $content[‘content‘], ‘nowtime‘=> date("Y-m-d H:i:s"), ‘type‘ => 2, //2代表聊天發信息 ‘speaker‘ => $frame->fd, ‘headimg‘ => $headimg ]; $this->ws->task($data); break; case ‘ptp‘: if(isset($content[‘want‘])){ //這是點擊了退出一對一的聊天,返回大廳按鈕 $user_fd=‘用戶‘.$frame->fd; $tid=$redis->get($user_fd); $tid=intval($tid); swoole_timer_clear($tid); //關閉檢測對方是否有發信息的定時器 }else{ //這是與某個人一對一聊天 echo "這是測試的:".$frame->fd; var_dump($frame->fd); $host=$frame->fd; $listener_id=$content[‘user_id‘]; echo "ser-push-message:".‘與‘."{$listener_id}".‘聊天‘."\n"; $res=$redis->HMGET($frame->fd,[‘content‘]); var_dump($res); $data=[ ‘listener‘ => $listener_id, ‘nowtime‘=> date("Y-m-d H:i:s"), ‘type‘ => 3, //3代點擊了與某個人聊天 ‘speaker‘ => $frame->fd, ]; //定時器用於一對一時查看對方是否有發送消息 $timer_id=swoole_timer_tick(2000,function($time_id) use($host,$redis,$listener_id){ echo "host:".$host." listener:".$listener_id." 1s-time_id:{$time_id}\n"; $res=$redis->hMget($host,[‘speaker‘]); if($res[‘speaker‘]&&$res[‘speaker‘]==$listener_id){ $message=$redis->hMget($host,[‘speaker_headimg‘,‘speaker‘,‘content‘,‘nowtime‘,‘listener‘]); //獲取這個人發來的信息 $data=[ ‘content‘ => $message[‘content‘], ‘nowtime‘ => $message[‘nowtime‘], ‘listener‘ => $message[‘listener‘], ‘speaker_headimg‘ => $message[‘speaker_headimg‘], ‘speaker‘ => $message[‘speaker‘], ‘time_id‘ => $time_id, ‘type‘ => 5, //定時器接收信息 ]; $this->ws->task($data); } }); $user_fd=‘用戶‘.$frame->fd; $redis->set($user_fd,$timer_id); $this->ws->task($data); } break; case ‘ptp_speak‘: //一對一發送內容時觸發 $listener_id=$content[‘listener‘]; echo "ser-push-message:{$frame->fd}".‘與‘."{$listener_id}".‘聊天‘."\n"; //從hash結構取出對應用戶的頭像 $speaker_headimg=$redis->hGet(‘links‘,$frame->fd); //$listener_headimg=$redis->hGet(‘links‘,$listener_id); //用task任務進行聊天 $nowtime=date("Y-m-d H:i:s"); $data=[ ‘listener‘ => $listener_id, ‘nowtime‘=> $nowtime, ‘type‘ => 4, //4代與某個人聊天 ‘speaker‘ => $frame->fd, ‘speaer_headimg‘ => $speaker_headimg, ‘content‘ => $content[‘content‘], // ‘listener_headimg‘ => $listener_headimg ]; //設置信息發送給誰的內容 $redis->hMset($listener_id,[‘speaker‘=>$frame->fd,‘speaker_headimg‘=>$speaker_headimg,‘listener‘=>$listener_id,‘content‘=>$content[‘content‘],‘nowtime‘=>$nowtime]); $this->ws->task($data); break; } } /** *執行task任務 */ public function onTask($serv,$taskId,$workerId,$data){ $redis = self::myRedis(); switch($data[‘type‘]){ case 1: $users=$redis->sMembers(‘chat‘); //獲取到所有在線人的id $data[‘usernum‘]=$redis->scard(‘chat‘); //統計在線人成員數 foreach($users as $user){ $this->ws->push($user,json_encode($data)); //把這個在線成員數發給每一個人 } return "task-people finish"; break; case 2: $speaker=$data[‘speaker‘]; $users=$redis->sMembers(‘chat‘); //獲取chat集合的所有在線人的id foreach($users as $user){ if($user==$speaker){ //如果這個id等於當前人的id,則定義speak字段為1,聊天信息框在右側 $data[‘speaker‘]=1; $this->ws->push($user,json_encode($data)); }else{ $data[‘speaker‘]=0; //如果這個id等於當前人的id,則定義speak字段為1,聊天信息框在左側 $this->ws->push($user,json_encode($data)); } } return "task-chat finish"; break; case 3: $this->ws->push($data[‘speaker‘],json_encode($data)); //3代表點擊了某個人一對一時,把這個人的id放在頭部 return "task3"; break; case 4: $this->ws->push($data[‘speaker‘],json_encode($data)); //4代表一對一時我方發送信息,然後我方需要顯示出來 return "task4"; break; case 5: $this->ws->push($data[‘listener‘],json_encode($data)); //5代表一對一時收到了對方的信息,然後我方顯示出來 $redis->del($data[‘listener‘]); return "task2".$data[‘time_id‘]; break; } } /** *task結束後執行的,在cli可以看到 */ public function onFinish($serv,$taskId,$data){ echo "finish-success:{$data}\n"; } /** *關閉時觸發 */ public function onClose($ws,$fd){ $redis = self::myRedis(); $redis->sRem(‘chat‘,$fd); //用戶關閉時,把這個人的id從chat集合中移除 $data=[ ‘type‘ => 1 //標記類型為統計在線人數 ]; $this->ws->task($data); //執行task任務,把在線人數重新計算發給全部在線用戶 echo "close:{$fd}"; } //把頭像和用戶id放進redis的hash結構 public function getheader($link_id){ $redis = self::myRedis(); $headimg=$redis->sPop(‘headimg‘); //隨機在頭像集合中取一個 $redis->hSet(‘links‘,$link_id,$headimg); //把用戶的id和頭像存進hash結構裏面 } } $obj = new Ws();
因為基本主要的代碼都放出來了,並且都有註釋,所以就不過多的解釋了。
然後就再說一下這聊天時的整個運作流程:
用戶進入聊天室 -> 前端觸發onopen -> 後端onopen -> 前端發信息send -> 後端接收信息onmessage -> 前端接收後端的返回信息onmessage -> 前後端監聽關閉onclose
最後,因為這個是個人的練手學習筆記,所以希望大家不喜勿噴,如果有問題的可以給我留言哦!
用swoole擴展 做的h5版聊天室筆記