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

具体效果:

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

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容