首页 技术 正文
技术 2022年11月19日
0 收藏 546 点赞 3,542 浏览 10820 个字

声明:该聊天室目前只有一对多,一对一的聊天功能,另外,因为没有使用到mysql,所以还存在比较多的缺陷地方,但知道原理就差不多了,这里主要分享下swoole简易的聊天室制作思路。

开发环境:centos7、redis、swoole

首先看看效果图

Swoole实现h5版聊天室笔记      Swoole实现h5版聊天室笔记   Swoole实现h5版聊天室笔记   Swoole实现h5版聊天室笔记

难点:这里一对多比较容易实现,就是简单的用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

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

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,020
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,513
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,359
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,142
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,772
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,850