zoukankan      html  css  js  c++  java
  • Swoole实现h5版聊天室笔记

    声明:该聊天室目前只有一对多,一对一的聊天功能,另外,因为没有使用到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}
    ";
    		// });
    		$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']}
    ";
    				//从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}".'聊天'."
    ";
    				$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}
    ";
    					$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}".'聊天'."
    ";
    				//从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}
    ";
    	}
    
    	/**
    	 *关闭时触发
    	 */
    	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

    最后,因为这个是个人的练手学习笔记,所以希望大家不喜勿喷,如果有问题的可以给我留言哦!

  • 相关阅读:
    Java中文语言处理HanLP
    python的jieba分词词性标注(转载)
    solr 自聚类实现
    IntelliJ IDEA 创建 java Maven项目
    javat Itext实践 pdf
    java 中PriorityQueue优先级队列使用方法
    java实现 tf-idf
    Solr6.6 IK 中文分词的配置和使用
    yaha分词
    实现自动文本摘要(python,java)
  • 原文地址:https://www.cnblogs.com/legendheng/p/9164091.html
Copyright © 2011-2022 走看看