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}\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版聊天室笔记的更多相关文章
- swoole实验版聊天室
“swoole实验版聊天室”是依据一堂swoole培训课内容改编的,结合了bootstrap前端框架.redis数据库.jquery框架等实现基本功能,只是体现了swoole的应用,并不是为了专门写个 ...
- 用Swoole+React 实现的聊天室
前后端分离的项目,使用 Swoole+React 实现的聊天室,整个项目的框架结构可以进行参考,前端 react+react-redux+react-router+react-ant 等等,后台使用 ...
- angular版聊天室|仿微信界面IM聊天|NG2+Node聊天实例
一.项目介绍 运用angular+angular-cli+angular-router+ngrx/store+rxjs+webpack+node+wcPop等技术实现开发的仿微信angular版聊天室 ...
- 如何利用WebSocket实现网页版聊天室
花了将近一周的时间终于完成了利用WebSocket完成网页版聊天室这个小demo,期间还走过了一段"看似弯曲"的道路,但是我想其实也不算是弯路吧,因为你走过的路必将留下你的足迹.这 ...
- 基于WebSocket实现网页版聊天室
WebSocket ,HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,其使用简单,应用场景也广泛,不同开发语言都用种类繁多的实现,仅Java体系中,Tomcat,Jetty,Sp ...
- Laravel + Swoole 打造IM简易聊天室
最近在学习Swoole,利用Swoole扩展让PHP生动了不少,本篇就来Swoole开发一款简易的IM聊天室 应用场景:实现简单的即时消息聊天室. (一)扩展安装 pecl install swool ...
- golang简易版聊天室
功能需求: 创建一个聊天室,实现群聊和单聊的功能,直接输入为群聊,@某人后输入为单聊 效果图: 群聊: 单聊: 服务端: package main import ( "fmt" ...
- WinForm版聊天室复习Socket通信
聊天室:服务器端-------------客户端 最终演示展示图: 一. 服务器端 对服务端为了让主窗体后台不处理具体业务逻辑,因此对服务端进行了封装,专门用来处理某个客户端通信的过程. 而由于通信管 ...
- 基于swoole实现多人聊天室
核心的swoole代码 基本的cs(client-sercer)结构不变,这里利用的是redis的哈希和set来储存和分组;从而达到了分组,统计,定时推送等功能;最后利用onclose事件来剔除断开的 ...
随机推荐
- 关于python线程池threadpool
#coding=utf-8 import time import threadpool def wait_time(n): print('%d\n' % n) time.sleep(2) #在线程池中 ...
- 用c写一个小的聊天室程序
1.聊天室程序——客户端 客户端我也用了select进行I/O复用,同时监控是否有来自socket的消息和标准输入,近似可以完成对键盘的中断使用. 其中select的监控里,STDOUT和STDIN是 ...
- 【题解】洛谷P1073 [NOIP2009TG] 最优贸易(SPFA+分层图)
次元传送门:洛谷P1073 思路 一开始看题目嗅出了强连通分量的气息 但是嫌长没打 听机房做过的dalao说可以用分层图 从来没用过 就参考题解了解一下 因为每个城市可以走好几次 所以说我们可以在图上 ...
- jFinal 2.2入门学习之一:搭建框架输出helloword
官方推荐用Eclipse IDE for Java EE Developers 做为开发环境 1.创建 Dynamic Web Project 2.修改 Default Output Folder,推 ...
- Linux 带宽、CPU、内存占用情况
iftop 查看带宽占用情况(总)yum install -y iftop 安装iftopnethogs 查看进程流量 curl http://218.5.73.233:8060/ip.php 查看出 ...
- Centos 7下Nagios的安装及配置
简介 Nagios 是一款自动化运维工具,可以协助运维人员监控服务器的运行状况,并且拥有报警功能.本文章将介绍其安装方法和详细的配置方法. nagios 监控服务应用指南 本地资源:负载,CPU,磁盘 ...
- 理解numpy dot函数
python代码 x = np.array([[1,3],[1,4]]) y = np.array([[2,2],[3,1]]) print np.dot(x,y) 结果 [[11 5] [14 6] ...
- Notes 20180310 : String第二讲_String的声明与创建
1 字符串的声明与创建 学习String的第一步就是创建(声明)字符串,我们在这里之所以分为创建和声明(其实是一个意思,都是创建字符串,但两者却有本质的区别)是因为String是一个很特殊的类,它的 ...
- 关于restful开发的疑惑
if 你没有了解过restful return; 一.疑惑 restful风格开发是有争议的,restful的设计是请求“resource”,然后只能对“resource”做CRUD操作.抽象于这 ...
- Oracle条件查询
Oracle条件查询 参考网址:http://www.oraclejsq.com/article/010100259.html Oracle条件查询时经常使用=.IN.LIKE.BETWEEN...A ...