这篇文章主要介绍“怎么用swoole + js + redis实现简易聊天室”,在日常操作中,相信很多人在怎么用swoole + js + redis实现简易聊天室问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么用swoole + js + redis实现简易聊天室”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
公司需要用到在线聊天功能,先写了个demo出来展示效果。
主要用到了swoole的websocket,task和redis的string,hash,set,zet等。
聊天室分为10个频道,可以切换频道,频道计数等。
目前还没做聊天内容的加密。
按需求,聊天内容可能会每个频道保留最近一百条,聊天加密的话考虑aes,也有可能不加。后续看情况了。
目前还没做异常处理。回头继续完善之后可能会再进行更新。
先上代码吧,有疑问可以留言交流。这里是html代码 可以自己引入jq地址。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocketTestPage</title>
<script src="/jquery.js" type="text/javascript"></script>
</head>
<body>
<input type="text" id="button">
<input type="button" onclick="senMsg()" value="发送">
<hr>
<textarea type="text" id="msgBox" ></textarea>
<script>
var wsUrl = "ws://39.100.xx.xx:8003/";
var webSocket = new WebSocket(wsUrl);
//实例对象onOpen属性
webSocket.onopen = function(evt){
changeMsg('聊天室已接入');
webSocket.send('{"gid":2041461173415,"uid":46676437104256,"msg_type":0}');
console.log("conected-swoole-success");
webSocket.send("已接入");
};
webSocket.onmessage = function(evt){
changeMsg( '收到消息:' + evt.data);
console.log("ws-return-data:" + evt.data);
};
webSocket.onclose = function(evt){
changeMsg( '已关闭');
console.log("close-ws-client");
};
webSocket.onerror = function(evt,e){
changeMsg( '已关闭' + evt.data + e);
console.log("error--" + evt.data);
};
function senMsg(){
val = $('#button').val();
webSocket.send(val);
}
function changeMsg(msg){
$('#msgBox').append(msg + "\n");
}
</script>
</body>
</html>
服务端代码
<?php
const HOST = "0.0.0.0";
const PORT = 8003;
const WORKER = 16;
const TASK = 10;
const S_NAME = 'server_';
const CHANNEL_MAX_SIZE = 10;
const ROOM_MAX_SIZE = 1000;
const CNL = 'zset_channel_num_list';//zset key 计数用
const CR = 'set_chat_room_';//频道编号 zset的member 以及set存储连接id
const GR = 'set_guild_room_';//公会聊天频道
const CF = 'hash_chat_fd_';//聊天用户信息 hash
const CL = 'list_chat_info_';//聊天信息 list
const GCL = 'list_guild_chat_info_';//公会聊天信息 list
const CL_MAX_SIZE = 20;//每个频道保留聊天信息上限
const SM = 'string_sys_msg_';//系统消息 string
class Ws
{
public $ws = null;
private $redis = null;
private $in_param = [
0 => ['uid','gid'],
1 => ['new_room','old_room'],
2 => ['level','data','room_id','uid','name','head'],
4 => ['level','data','gid','uid','name','head'],
];
public function __construct() {
if(!isset($this->redis)){
$this->redis = $this->_getRedis();
$this->redis->select(10);
$this->redis->flushDB();
}
$this->_resetChatNum();
if(!isset($this->ws)){
$this->ws = new swoole_websocket_server( HOST, PORT);
$this->ws->set(
[
'worker_num' => WORKER,
'task_worker_num' => TASK,
]
);
$this->ws->on("open", [$this,"onOpen"]);
$this->ws->on("message", [$this,"onMessage"]);
$this->ws->on("close", [$this,"onClose"]);
$this->ws->on("task", [$this,"onTask"]);
$this->ws->on("finish", [$this,"onFinish"]);
$this->ws->start();
}
}
public function onOpen($ws, $request) {
$msg = array(
'msg_type' => 0,
);
$this->_pushSysNotice($request->fd);
$ws->push($request->fd, json_encode($msg));
}
private function _pushOldMsg($fd, $channel, $guild = false){
$m = $this->redis->lRange(CL . $channel,0,CL_MAX_SIZE);
$msg = [];
foreach($m as $v){
$msg[] = json_decode($v,true);
}
if(!empty($msg))
$this->ws->task(['fds'=>[$fd],'msg'=>$msg]);
$this->redis->lTrim(CL . $channel,0,CL_MAX_SIZE);
if($guild){
$g_m = $this->redis->lRange(GCL . $guild,0,CL_MAX_SIZE);
$g_msg = [];
foreach($g_m as $v){
$g_msg[] = json_decode($v,true);
}
if(!empty($g_msg))
$this->ws->task(['fds'=>[$fd],'msg'=>$g_msg]);
$this->redis->lTrim(GCL . $guild,0,CL_MAX_SIZE);
}
}
private function _pushSysNotice($fd){
$this->redis->select(0);
$keys = $this->redis->keys(SM . '*');
$msg = array(
'msg_type' => 3,
);
if(!empty($keys)){
foreach($keys as $v){
$s_msg = $this->redis->get($v);
$msg['data'] = $s_msg;
$this->ws->push($fd, json_encode($msg));
}
}
$this->redis->select(10);
}
public function onMessage($ws, $frame) {
$data = json_decode($frame->data,true);
$ret = [];
switch($data['msg_type']){
case 0://初始化
if($this->_checkParam($this->in_param[0], $data)){
$ret = $this->_setConnectInfo($frame->fd, $data);
if($data['gid'] > 0)
$this->_pushOldMsg($frame->fd,$ret['room_id'],$data['gid']);
}else{
$ret = ['msg_type'=>'no param'];
}
break;
case 1://切换频道
if($this->_checkParam($this->in_param[1], $data)){
$ret = $this->_changeChannel($frame->fd, $data);
}else{
$ret = ['msg_type'=>'no param'];
}
break;
case 2://聊天
if($this->_checkParam($this->in_param[2], $data)){
$ret = $this->_pushChatInfo($frame->fd, $data);
}else{
$ret = ['msg_type'=>'no param'];
}
break;
case 3:
break;
case 4://公会聊天
if($this->_checkParam($this->in_param[4], $data)){
$ret = $this->_pushChatInfo($frame->fd, $data,true);
}else{
$ret = ['msg_type'=>'no param'];
}
break;
default:
$ws->push($frame->fd, "error");
break;
}
echo "fd: {$frame->fd} Message: {$frame->data} \n";
if(!empty($ret)){
$ws->push($frame->fd, json_encode($ret));
}
}
public function onClose($ws, $fd) {
$this->_delChatInfo($fd, true);
}
public function onTask($ws, $taskId, $workerId, $data) {
foreach($data['fds'] as $v){
$this->ws->push($v,json_encode($data['msg']));
}
return $taskId;
}
public function onFinish($ws, $taskId, $data) {
echo "task-{$taskId} is end\n";
}
//初始化频道计数器
private function _resetChatNum(){
for($i = 1; $i <= CHANNEL_MAX_SIZE; $i++){
$this->redis->zAdd(CNL, 0, CR . $i);
}
}
//获取人数最少频道
private function _getSuggestRoomId(){
$chat_list = $this->redis->zRange(CNL,0,0);
$no = substr($chat_list[0],14);
if(empty($no) || (int)$no < 1 || (int)$no > CHANNEL_MAX_SIZE){
$no = mt_rand(1,CHANNEL_MAX_SIZE);
}
return (int)$no;
}
//连接时设置推荐频道
private function _setConnectInfo($fd, $data){
$channel = $this->_getSuggestRoomId();
$this->_setChannelInfo($fd,$channel);
$this->redis->hSet(CF.$fd, 'uid',$data['uid']);
$this->redis->hSet(CF.$fd, 'gid',$data['gid']);
if(!empty($data['gid'])){
$this->redis->sAdd(GR.$data['gid'], $fd);
}
return array(
'msg_type' => 1,
'data' => $channel,
'room_id' => $channel,
'code' => 0,
'fd' => $fd,
);
}
//设置频道相关数据
private function _setChannelInfo($fd, $channel){
//存入fd
$this->redis->sAdd(CR . $channel,$fd);
//变更计数器
$this->redis->zIncrBy(CNL, 1, CR . $channel);
//存入用户频道信息
$this->redis->hSet(CF.$fd, 'channel',(int)$channel);
}
//校验字段
private function _checkParam($param, $data){
foreach($param as $v){
if(!isset($data[$v]))return false;
}
return true;
}
//切换频道
private function _changeChannel($fd, $data){
$this->_delChatInfo($fd);
$this->_setChannelInfo($fd,$data['new_room']);
$this->_pushOldMsg($fd,$data['new_room']);
return array(
'msg_type' => 1,
'data' => $data['new_room'],
'code' => 1,
);
}
//关闭连接时清除相关内容
private function _delChatInfo($fd, $del = false){
$channel = $this->redis->hget(CF.$fd,'channel');
if(empty($channel)) return true;
$this->redis->sRem(CR .$channel, $fd);
if($del){
$this->redis->del(CF.$fd);
}
$this->redis->zIncrBy(CNL, -1, CR . $channel);
}
//推送聊天信息
private function _pushChatInfo($fd, $data, $guild = false){
if(!$guild){
$user = $this->redis->hGetAll(CF.$fd);
$fds = $this->redis->sMembers(CR . $user['channel']);
$this->redis->lPush(CL . $user['channel'],json_encode($data));
$type = 2;
}else{
$fds = $this->redis->sMembers(GR . $data['gid']);
$this->redis->lPush(GCL . $data['gid'],json_encode($data));
$type = 4;
}
$msg = array(
'msg_type' => $type,
'uid' => $data['uid'],
'name' => $data['name'],
'data' => $data['data'],
'level' => $data['level'],
'head' => $data['head'],
'code' => $type,
);
$this->ws->task(['fds'=>$fds,'msg'=>$msg]);
return [];
}
//初始化redis资源
private function _getRedis()
{
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
return $redis;
}
}
//启动
new Ws();
请求示例
{"new_room":8,"old_room":1,"msg_type":1} //切换频道
{"level":10,"data":"嗷嗷嗷啊","room_id":1,"msg_type":2,"uid":xxxx,"name":1028,"head":1}//聊天
{"gid":xxxx,"uid":xxxx,"msg_type":0} //初始化,其实这块本来想写道open里面但是这样的话需要前端改动,就先这样了。
到此,关于“怎么用swoole + js + redis实现简易聊天室”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/u/3538113/blog/3083785