首页 > 数据库 >swoole+websocket+redis实现一对一聊天

swoole+websocket+redis实现一对一聊天

时间:2023-04-03 17:34:49浏览次数:47  
标签:box websocket swoole text border redis height var data

 

如同web端的QQ和微信一样,这是一个web端的聊天程序。

环境:linux(centos) + php7.2 + swoole扩展 + redis + mysql

Redis 实现每个连接websocket的服务都唯一绑定一个用户。通过 用户账号 = websocket fd 存到redis中。

Mysql 实现离线消息池。如果一个用户不在线,则其他用户发送给他的消息暂时存储在mysql。待该用户上线时,再从离线消息池取出发送。

此处是看了

还是感谢大神。

具体参考代码和相应注释:

服务端代码:

复制代码
<?php
$server = new swoole_websocket_server("0.0.0.0", 9052);
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$db = new mysqli('127.0.0.1', 'test', 'test', 'thinkphp5');

$server->on('open', function (swoole_websocket_server $server, $request) {
    echo "server: handshake success with fd{$request->fd}\n";//$request->fd 是客户端id
});

$server->on('message', function (swoole_websocket_server $server, $frame) {
    $data = json_decode($frame->data,true); 
    if($data['flag'] == 'init'){
        //用户刚连接的时候初始化,每个用户登录时记录该用户对应的fd
        $GLOBALS['redis']->set($data['from'], $frame->fd);
        //处理发给该用户的离线消息
        $sql = "SELECT `from`,content FROM thinkphp5.app_offline WHERE `to`='{$data['from']}' AND `from`='{$data['to']}' AND `status`='0' ORDER BY addtime ASC;";
        if ($result = $GLOBALS['db']->query($sql)) {
            $re = array();
            while ($row = $result->fetch_assoc()) {
                array_push($re, $row);
            }
            $result->free();
            foreach($re as $content){
                $content = json_encode($content);
                $server->push($frame->fd , $content);
            }
            //设置消息池中的消息为已发送
            $sql = "UPDATE thinkphp5.app_offline SET `status`=1 WHERE `to`='{$data['from']}' AND `from`='{$data['to']}';";
            $GLOBALS['db']->query($sql);
        }
    }else if($data['flag'] == 'msg'){
        //非初始化的信息发送,一对一聊天,根据每个用户对应的fd发给特定用户
        $tofd = $GLOBALS['redis']->get($data['to']); //消息要发给谁
        $fds = []; //所有在线的用户(打开聊天窗口的用户)
        foreach($server->connections as $fd){
            array_push($fds, $fd);
        }
        if(in_array($tofd,$fds)){
            $tmp['from'] = $data['from']; //消息来自于谁
            $tmp['content']  = $data['content']; //消息内容
            $re = json_encode($tmp);
            $server->push($tofd , $re);
        }else{
            //该玩家不在线(不在聊天室内),将信息发送到离线消息池
            $time = time();
            $sql = "INSERT INTO thinkphp5.app_offline (`to`,`from`,`content`,`status`,`addtime`) VALUES ('{$data['to']}','{$data['from']}','{$data['content']}','0','{$time}');";
            $GLOBALS['db']->query($sql);
        }
    }else if($data['flag'] == 'group'){
        //todo 群聊
        
    }else if($data['flag'] == 'all'){
        //全站广播
        foreach($server->connections as $fd){
            $server->push($fd , $data);
        }
    }  
});

$server->on('close', function ($ser, $fd) {
    echo "client {$fd} closed\n";
});

$server->start();
复制代码

 

客户端代码 :

 

复制代码
<!DOCTYPE html>
<html>
<head>
    <title>XST-app</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
    <meta name="viewport" content="width=device-width, initial-scale=0.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
    <meta name="keywords" content="test" />
    <meta name="description" content="test" />
    <meta name="author" content="XST-APP" />
    <meta content="yes" name="apple-mobile-web-app-capable" />
    <meta content="black" name="apple-mobile-web-app-status-bar-style" />
    <meta content="telephone=no" name="format-detection" />
  <style type="text/css">
    body{background:url(/static/images/yuyin_bg.jpg);background-size:100%;}
    @media all and (min-width: 640px) {
        body,html,.wenwen-footer,.speak_window{width:640px!important;margin:0 auto}
        .speak_window,.wenwen-footer{left:50%!important;margin-left:-320px}
    }
    input,button{outline:none;}
    .wenwen-footer{width:100%;position:fixed;bottom:-5px;left:0;background:#fff;padding:3%;border-top:solid 1px #ddd;box-sizing:border-box;}
    .wenwen_btn,.wenwen_help{width:15%;text-align:center;}
    .wenwen_btn img,.wenwen_help img{height:40px;}
    .wenwen_text{height:40px;border-radius:5px;border:solid 1px #636162;box-sizing:border-box;width:66%;text-align:center;overflow:hidden;margin-left:2%;}
    .circle-button{padding:0 5px;}
    .wenwen_text .circle-button{font-size:14px;color:#666;line-height:38px;}
    .write_box{background:#fff;width:100%;height:40px;line-height:40px;}
    .write_box input{height:40px;padding:0 5px;line-height:40px;width:100%;box-sizing:border-box;border:0;}
    .wenwen_help button{width:95%;background:#42929d;color:#fff;border-radius:5px;border:0;height:40px;}
    #wenwen{height:100%;}
    .speak_window{overflow-y:scroll;height:100%;width:100%;position:fixed;top:50px;left:0;}
    .speak_box{margin-bottom:70px;padding:10px;}
    .question,.answer{margin-bottom:1rem;}
    .question{text-align:right;}
    .question>div{display:inline-block;}
    .left{float:left;}
    .right{float:right;}
    .clear{clear:both;}
    .heard_img{height:60px;width:60px;border-radius:5px;overflow:hidden;background:#ddd;}
    .heard_img img{width:100%;height:100%}
    .question_text,.answer_text{box-sizing:border-box;position:relative;display:table-cell;min-height:60px;}
    .question_text{padding-right:20px;}
    .answer_text{padding-left:20px;}
    .question_text p,.answer_text p{border-radius:10px;padding:.5rem;margin:0;font-size:14px;line-height:28px;box-sizing:border-box;vertical-align:middle;display:table-cell;height:30px;word-wrap:break-word;}
    .answer_text p{background:#fff;}
    .question_text p{background:#42929d;color:#fff;text-align:left;}
    .question_text i,.answer_text i{width:0;height:0;border-top:5px solid transparent;border-bottom:5px solid transparent;position:absolute;top:25px;}
    .answer_text i{border-right:10px solid #fff;left:10px;}
    .question_text i{border-left:10px solid #42929d;right:10px;}
    .answer_text p a{color:#42929d;display:inline-block;}
    .write_list{position:absolute;left:0;width:100%;background:#fff;border-top:solid 1px #ddd;padding:5px;line-height:30px;}
  </style>
</head>

<body>
<div id="header" class="head">
        <div class="wrap">
                <i class="menu_back"><a href="javascript:history.go(-1);"></a></i>
                <div class="title">
                        <span class="title_d"><p>与 {$tonickname} 的聊天</p></span>
                        <div class="clear"></div>
                </div>
                <!--i class="menu_share"></i-->
        </div>
</div>
<input type="hidden" name="myemail" id="myemail" value="{$myemail}" />
<input type="hidden" name="mynickname" id="mynickname" value="{$mynickname}" />
<input type="hidden" name="myavatar" id="myavatar" value="{$myavatar}" />
<input type="hidden" name="toemail" id="toemail" value="{$toemail}" />
<input type="hidden" name="tonickname" id="tonickname" value="{$tonickname}" />
<input type="hidden" name="toavatar" id="toavatar" value="{$toavatar}" />

<!-- 对话内容 -->
<div class="speak_window">
    <div class="speak_box">

    </div>
</div>

<!-- 内容输入-->
<div class="wenwen-footer">
    <div class="wenwen_btn left"><img src="/static/images/jp_btn.png"></div>
    <div class="wenwen_text left">
        <div class="write_box"><input type="text" class="left" onKeyUp="keyup()" maxlength="100" placeholder="请输入信息(100字以内)..." /></div>   
    </div>
    <div class="wenwen_help right">
            <button onClick="send()" class="right">发送</button>
    </div>
    <div style="opacity:0;" class="clear"></div>
</div>

<script type="text/javascript">
    if ("WebSocket" in window){
        var ws = new WebSocket("ws://192.168.0.1:9052");
        ws.onopen = function(){
            console.log("握手成功");
            var myemail = $("#myemail").val();
            var toemail = $("#toemail").val();
            var arr = {"flag":"init","from":myemail,"to":toemail};
            var str = JSON.stringify(arr);
            ws.send(str);
        };
        ws.onmessage = function(e){
            var toemail = $("#toemail").val();
            var toavatar = $("#toavatar").val();
            var obj = JSON.parse(e.data);
            console.log(e.data);
            //但同时与两个人聊天时,可能两个人的消息都会出现在当前窗口,所以此处加个判断,此窗口只接收当前聊天对象的消息,其他则忽略
            if(obj.from === toemail){
                var ans  = '<div class="answer"><div class="heard_img left"><img src="'+toavatar+'"></div>';
                    ans += '<div class="answer_text"><p>'+obj.content+'</p><i></i>';
                    ans += '</div></div>';
                    $('.speak_box').append(ans);
                    for_bottom();
            }
        };
        ws.onerror = function(){
            console.log("error");
            var  str  = '<div class="question">';
            str += '<div class="heard_img right"><img src="/static/images/xitong.jpg"></div>';
            str += '<div class="question_text clear"><p>聊天服务器出现异常,暂时无法提供服务。</p><i></i>';
            str += '</div></div>';
            $('.speak_box').append(str);
            $('.write_box input').val('');
            $('.write_box input').focus();
            autoWidth();
            for_bottom();
        };

        function send() {
            var content = $('.write_box input').val();
        if(content === ''){
            alert('请输入消息!');
            $('.write_box input').focus();
        }else{
                var toemail = $("#toemail").val();
                var myemail = $("#myemail").val();
                var myavatar = $("#myavatar").val();
                var arr = {"flag":"msg","to":toemail,"from":myemail,"content":content};
                var msg = JSON.stringify(arr);
                console.log(msg);
                ws.send(msg);    
                var  str  = '<div class="question">';
                str += '<div class="heard_img right"><img src="'+myavatar+'"></div>';
                str += '<div class="question_text clear"><p>'+content+'</p><i></i>';
                str += '</div></div>';
            $('.speak_box').append(str);
            $('.write_box input').val('');
            $('.write_box input').focus();
            autoWidth();
            for_bottom();
            }
        
        }
    }else{
        alert("您的浏览器不支持 WebSocket!");
    }
           
    function for_bottom(){
    var speak_height = $('.speak_box').height();
    $('.speak_box,.speak_window').animate({scrollTop:speak_height},500);
    }
    
    function autoWidth(){
    $('.question_text').css('max-width',$('.question').width()-60);
    }
    
    autoWidth();
    
</script>

</body>
</html>
复制代码

数据库DB:

复制代码
CREATE TABLE `app_offline` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `from` varchar(50) DEFAULT NULL COMMENT '离线发送方',
  `to` varchar(50) DEFAULT NULL COMMENT '离线接收方',
  `content` varchar(1000) DEFAULT NULL COMMENT '发送的离线内容',
  `status` tinyint(4) DEFAULT '0' COMMENT '发送状态:0-未发送,1-已发送',
  `addtime` int(11) DEFAULT NULL COMMENT '发送方发送时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
复制代码

标签:box,websocket,swoole,text,border,redis,height,var,data
From: https://www.cnblogs.com/yzl042349/p/17283724.html

相关文章

  • redis__windows系统下启动命令
     cmd进入redis目录下输入:redis-server.exeredis.windows.conf  windows系统开机自动开启redis服务:进入redis文件夹,输入redis-server.exe--service-installredis.windows.conf--loglevelverbose......
  • redis介绍
         ......
  • redis主从复制
     一、主从复制定义1、主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master),后者称为从节点(Slave);数据的复制是单向的,只能由主节点到从节点。默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能......
  • redis哨兵和集群
     一、redis哨兵主从切换技术的方法是:当服务器宕机后,需要手动一台从机切换为主机,这需要人工干预,不仅费时费力而且还会造成一段时间内服务不可用。为了解决主从复制的缺点,就有了哨兵机制。 哨兵的核心功能:在主从复制的基础上,哨兵引入了主节点的自动故障转移。 1、哨兵模式......
  • redis使用setnx+lua实现分布式锁
    在Redis中,使用SETEX命令(对应RedisTemplate的setIfAbsent方法)可以实现一个最简易的分布锁。SETEX命令当key不存在的话,才会设置key的值,如果可以已经存在,就不做任何操作。为了避免锁无法被释放,就给这个key(也就是锁)设置一个过期时间。为了保证解锁操作的原子性,使用Lua脚本进行释放锁......
  • Redis6 集群单机安装
    Redis6集群单机安装官网下载https://download.redis.io/releases/redis-6.2.6.tar.gzhttp://download.redis.io/redis-stable.tar.gz文档参考https://redis.io/documentation解压编译#安装编译需要的tclyum-yinstallgcctclrm-rf/data/redismkdir-p/data/re......
  • Redis下载与安装
    在Linux系统安装Redis步骤:1、将Redis安装包上传到Linux2、解压安装包,命令:tar-zxvfredis-4.0.0.tar.gz-C/usr/local3、安装Redis的依赖环境gcc,命令:yuminstallgcc-c++4、进入/usr/local/redis-4.0.0,进行编译,命令:make5、进入redis的src目录,进行安装,命令:makeinstall 在......
  • 决战圣地玛丽乔亚Day46----Redis哨兵模式
    哨兵模式Sentinel:  自动感知Master故障并选择一个Slave切换为Master,实现故障的自动转移能力。1.监控:持续监控主从是否健康,是否处于预期的工作状态.2.主从的动态切换:当Master故障后,哨兵启动自动故障恢复:从slave中选一个新的master3.通知机制:竞选出来新的master后,通知客户......
  • Docker - 安装Redis
    目录前言环境安装Redis下载Redis安装Redis配置Redis前言记录下Docker下Redis的安装环境Centos7+Docker23.0.1+Redis6.2.7安装Redis下载Redis指定版本下载redis,版本可通过仓库https://hub.docker.com查看dockerpullredis:6.2.7安装Redis创建redis配置......
  • Day 21 21.1 数据库之redis
    Redisredis介绍定义Redis(RemoteDictionaryServer,远程字典服务)是一个使用ANSIC编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库,是NoSQL数据库。redis的出现主要是为了替代早期的Memcache缓存系统的。map内存型(数据存放在内存中)的非关系型(nosql)key-......