1. 程式人生 > >用swoole擴展 做的h5版聊天室筆記

用swoole擴展 做的h5版聊天室筆記

一個人 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版聊天室筆記