声明:该聊天室目前只有一对多,一对一的聊天功能,另外,因为没有使用到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版聊天室笔记的更多相关文章

  1. swoole实验版聊天室

    “swoole实验版聊天室”是依据一堂swoole培训课内容改编的,结合了bootstrap前端框架.redis数据库.jquery框架等实现基本功能,只是体现了swoole的应用,并不是为了专门写个 ...

  2. 用Swoole+React 实现的聊天室

    前后端分离的项目,使用 Swoole+React 实现的聊天室,整个项目的框架结构可以进行参考,前端 react+react-redux+react-router+react-ant 等等,后台使用 ...

  3. angular版聊天室|仿微信界面IM聊天|NG2+Node聊天实例

    一.项目介绍 运用angular+angular-cli+angular-router+ngrx/store+rxjs+webpack+node+wcPop等技术实现开发的仿微信angular版聊天室 ...

  4. 如何利用WebSocket实现网页版聊天室

    花了将近一周的时间终于完成了利用WebSocket完成网页版聊天室这个小demo,期间还走过了一段"看似弯曲"的道路,但是我想其实也不算是弯路吧,因为你走过的路必将留下你的足迹.这 ...

  5. 基于WebSocket实现网页版聊天室

    WebSocket ,HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,其使用简单,应用场景也广泛,不同开发语言都用种类繁多的实现,仅Java体系中,Tomcat,Jetty,Sp ...

  6. Laravel + Swoole 打造IM简易聊天室

    最近在学习Swoole,利用Swoole扩展让PHP生动了不少,本篇就来Swoole开发一款简易的IM聊天室 应用场景:实现简单的即时消息聊天室. (一)扩展安装 pecl install swool ...

  7. golang简易版聊天室

    功能需求: 创建一个聊天室,实现群聊和单聊的功能,直接输入为群聊,@某人后输入为单聊 效果图: 群聊:   单聊: 服务端: package main import ( "fmt" ...

  8. WinForm版聊天室复习Socket通信

    聊天室:服务器端-------------客户端 最终演示展示图: 一. 服务器端 对服务端为了让主窗体后台不处理具体业务逻辑,因此对服务端进行了封装,专门用来处理某个客户端通信的过程. 而由于通信管 ...

  9. 基于swoole实现多人聊天室

    核心的swoole代码 基本的cs(client-sercer)结构不变,这里利用的是redis的哈希和set来储存和分组;从而达到了分组,统计,定时推送等功能;最后利用onclose事件来剔除断开的 ...

随机推荐

  1. Monkeyrunner命令

    1.使用Monkeyrunner脚本命令时,需要导入模块才能使用模块的脚本命令,Monkeyrunner的常用模块有 MonkeyRunner,MonkeyDevice,MonkeyImage,Mon ...

  2. Java之关于JSTL引入问题

    错误信息:Can not find the tag library descriptor for “http://java.sun.com/jstl/core”JSTL taglib需要jstl.ja ...

  3. nginx开启gzip

    gzip on; gzip_min_length 5k; gzip_buffers 4 16k; #gzip_http_version 1.0; gzip_comp_level 3; gzip_typ ...

  4. Webdriver API中文版

    Webdriver API中文版 1.1   下载selenium2.0的lib包 http://code.google.com/p/selenium/downloads/list 官方UserGui ...

  5. 什么是工厂函数?Python 中工厂函数怎么理解?(转)

    所谓工厂函数就是指这些内建函数都是类对象(实际上是类), 当你调用它们时,实际上是创建了一个类实例. type():查看类型

  6. Java导包后在测试类中执行正确但在Servlet中执行错误报ClassNotFoundException或者ClassDefNotFoundException解决办法

    将原来导的包remove from build path,并复制到Web-root下的lib目录中,再add to build path,

  7. CORS跨域实现思路及相关解决方案

    本篇包括以下内容: CORS 定义 CORS 对比 JSONP CORS,BROWSER支持情况 主要用途 Ajax请求跨域资源的异常 CORS 实现思路 安全说明 CORS 几种解决方案 自定义CO ...

  8. list 去重复元素

    public static List removeDuplicate(List list){ List listTemp = new ArrayList(); for(int i=0;i<lis ...

  9. mysql数据库用户密码管理

    创建用户:create user 'oracle'@'localhost' identified by 'password'; MySQL 8以上:alter user 'root'@'localho ...

  10. laychat聊天功能

    windows版本:1.直接下载laychat聊天室压缩包,并解压到PHPstudy本地PHP环境中去:2.进入E:\PHPTutorial\WWW\laychat-master\vendor\Wor ...