• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    PHP用swoole+websocket和redis实现web一对一聊天

    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);" rel="external nofollow" >/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>
    
    

    数据表结构:

    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
    

    具体效果:


    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    您可能感兴趣的文章:
    • windows系统php环境安装swoole具体步骤
    • php使用Swoole实现毫秒级定时任务的方法
    • php使用goto实现自动重启swoole、reactphp、workerman服务的代码
    • PHP Swoole异步读取、写入文件操作示例
    • PHP之Swoole学习安装教程
    上一篇:基于thinkphp6.0的success、error实现方法
    下一篇:PHPUnit + Laravel单元测试常用技能
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    PHP用swoole+websocket和redis实现web一对一聊天 PHP,用,swoole+websocket,和,redis,