当前位置:首页 > PHP教程 > php高级应用 > 列表

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

发布:smiling 来源: PHP粉丝网  添加日期:2022-01-21 20:36:56 浏览: 评论:0 

这篇文章主要介绍了PHP用swoole+websocket和redis实现web一对一聊天,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

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

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

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

  1. <?php 
  2. $server = new swoole_websocket_server("0.0.0.0", 9052); 
  3. $redis = new Redis(); 
  4. $redis->connect('127.0.0.1', 6379); 
  5. $db = new mysqli('127.0.0.1''test''test''thinkphp5'); 
  6.  
  7. $server->on('open'function (swoole_websocket_server $server$request) { 
  8.  echo "server: handshake success with fd{$request->fd}\n";//$request->fd 是客户端id 
  9. }); 
  10.  
  11. $server->on('message'function (swoole_websocket_server $server$frame) { 
  12.  $data = json_decode($frame->data,true);  
  13.  if($data['flag'] == 'init'){ 
  14.   //用户刚连接的时候初始化,每个用户登录时记录该用户对应的fd 
  15.   $GLOBALS['redis']->set($data['from'], $frame->fd); 
  16.   //处理发给该用户的离线消息 
  17.   $sql = "SELECT `from`,content FROM thinkphp5.app_offline WHERE `to`='{$data['from']}' AND `from`='{$data['to']}' AND `status`='0' ORDER BY addtime ASC;"
  18.   if ($result = $GLOBALS['db']->query($sql)) { 
  19.    $re = array(); 
  20.    while ($row = $result->fetch_assoc()) { 
  21.     array_push($re$row); 
  22.    } 
  23.    $result->free(); 
  24.    foreach($re as $content){ 
  25.     $content = json_encode($content); 
  26.     $server->push($frame->fd , $content); 
  27.    } 
  28.    //设置消息池中的消息为已发送 
  29.    $sql = "UPDATE thinkphp5.app_offline SET `status`=1 WHERE `to`='{$data['from']}' AND `from`='{$data['to']}';"
  30.    $GLOBALS['db']->query($sql); 
  31.   } 
  32.  }else if($data['flag'] == 'msg'){ 
  33.   //非初始化的信息发送,一对一聊天,根据每个用户对应的fd发给特定用户 
  34.   $tofd = $GLOBALS['redis']->get($data['to']); //消息要发给谁 
  35.   $fds = []; //所有在线的用户(打开聊天窗口的用户) 
  36.   foreach($server->connections as $fd){ 
  37.    array_push($fds$fd); 
  38.   } 
  39.   if(in_array($tofd,$fds)){ 
  40.    $tmp['from'] = $data['from']; //消息来自于谁 
  41.    $tmp['content'] = $data['content']; //消息内容 
  42.    $re = json_encode($tmp); 
  43.    $server->push($tofd , $re); 
  44.   }else
  45.    //该玩家不在线(不在聊天室内),将信息发送到离线消息池 
  46.    $time = time(); 
  47.    $sql = "INSERT INTO thinkphp5.app_offline (`to`,`from`,`content`,`status`,`addtime`) VALUES ('{$data['to']}','{$data['from']}','{$data['content']}','0','{$time}');"
  48.    $GLOBALS['db']->query($sql); 
  49.   } 
  50.  }else if($data['flag'] == 'group'){ 
  51.   //todo 群聊 
  52.     
  53.  }else if($data['flag'] == 'all'){ 
  54.   //全站广播 
  55.   foreach($server->connections as $fd){ 
  56.    $server->push($fd , $data); 
  57.   } 
  58.  }  
  59. }); 
  60.  
  61. $server->on('close'function ($ser$fd) { 
  62.  echo "client {$fd} closed\n"
  63. }); 
  64.  
  65. $server->start(); 

客户端代码:

  1. <!DOCTYPE html> 
  2. <html> 
  3. <head> 
  4.  <title>XST-app</title> 
  5.  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  6.  <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" /> 
  7.  <meta name="viewport" content="width=device-width, initial-scale=0.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" /> 
  8.  <meta name="keywords" content="test" /> 
  9.  <meta name="description" content="test" /> 
  10.  <meta name="author" content="XST-APP" /> 
  11.  <meta content="yes" name="apple-mobile-web-app-capable" /> 
  12.  <meta content="black" name="apple-mobile-web-app-status-bar-style" /> 
  13.  <meta content="telephone=no" name="format-detection" /> 
  14.   <style type="text/css"
  15.  body{background:url(/static/images/yuyin_bg.jpg);background-size:100%;} 
  16.  @media all and (min-width: 640px) { 
  17.   body,html,.wenwen-footer,.speak_window{width:640px!important;margin:0 auto} 
  18.   .speak_window,.wenwen-footer{left:50%!important;margin-left:-320px} 
  19.  } 
  20.  input,button{outline:none;} 
  21.  .wenwen-footer{width:100%;position:fixed;bottom:-5px;left:0;background:#fff;padding:3%;border-top:solid 1px #ddd;box-sizing:border-box;} 
  22.  .wenwen_btn,.wenwen_help{width:15%;text-align:center;} 
  23.  .wenwen_btn img,.wenwen_help img{height:40px;} 
  24.  .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%;} 
  25.  .circle-button{padding:0 5px;} 
  26.  .wenwen_text .circle-button{font-size:14px;color:#666;line-height:38px;} 
  27.  .write_box{background:#fff;width:100%;height:40px;line-height:40px;} 
  28.  .write_box input{height:40px;padding:0 5px;line-height:40px;width:100%;box-sizing:border-box;border:0;} 
  29.  .wenwen_help button{width:95%;background:#42929d;color:#fff;border-radius:5px;border:0;height:40px;} 
  30.  #wenwen{height:100%;} 
  31.  .speak_window{overflow-y:scroll;height:100%;width:100%;position:fixed;top:50px;left:0;} 
  32.  .speak_box{margin-bottom:70px;padding:10px;} 
  33.  .question,.answer{margin-bottom:1rem;} 
  34.  .question{text-align:right;} 
  35.  .question>div{display:inline-block;} 
  36.  .left{float:left;} 
  37.  .right{float:right;} 
  38.  .clear{clear:both;} 
  39.  .heard_img{height:60px;width:60px;border-radius:5px;overflow:hidden;background:#ddd;} 
  40.  .heard_img img{width:100%;height:100%} 
  41.  .question_text,.answer_text{box-sizing:border-box;position:relative;display:table-cell;min-height:60px;} 
  42.  .question_text{padding-right:20px;} 
  43.  .answer_text{padding-left:20px;} 
  44.  .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;} 
  45.  .answer_text p{background:#fff;} 
  46.  .question_text p{background:#42929d;color:#fff;text-align:left;} 
  47.  .question_text i,.answer_text i{width:0;height:0;border-top:5px solid transparent;border-bottom:5px solid transparent;position:absolute;top:25px;} 
  48.  .answer_text i{border-right:10px solid #fff;left:10px;} 
  49.  .question_text i{border-left:10px solid #42929d;right:10px;} 
  50.  .answer_text p a{color:#42929d;display:inline-block;} 
  51.  .write_list{position:absolute;left:0;width:100%;background:#fff;border-top:solid 1px #ddd;padding:5px;line-height:30px;} 
  52.   </style> 
  53. </head> 
  54.  
  55. <body> 
  56. <div id="header" class="head"
  57.   <div class="wrap"
  58.     <i class="menu_back"><a href="javascript:history.go(-1);" rel="external nofollow" ></a></i> 
  59.     <div class="title"
  60.       <span class="title_d"><p>与 {$tonickname} 的聊天</p></span> 
  61.       <div class="clear"></div> 
  62.     </div> 
  63.     <!--i class="menu_share"></i--> 
  64.   </div> 
  65. </div> 
  66. <input type="hidden" name="myemail" id="myemail" value="{$myemail}" /> 
  67. <input type="hidden" name="mynickname" id="mynickname" value="{$mynickname}" /> 
  68. <input type="hidden" name="myavatar" id="myavatar" value="{$myavatar}" /> 
  69. <input type="hidden" name="toemail" id="toemail" value="{$toemail}" /> 
  70. <input type="hidden" name="tonickname" id="tonickname" value="{$tonickname}" /> 
  71. <input type="hidden" name="toavatar" id="toavatar" value="{$toavatar}" /> 
  72.  
  73. <!-- 对话内容 --> 
  74. <div class="speak_window"
  75.  <div class="speak_box"
  76.  
  77.  </div> 
  78. </div> 
  79. <!-- 内容输入--> 
  80. <div class="wenwen-footer"
  81.  <div class="wenwen_btn left"><img src="/static/images/jp_btn.png"></div> 
  82.  <div class="wenwen_text left"
  83.   <div class="write_box"><input type="text" class="left" onKeyUp="keyup()" maxlength="100" placeholder="请输入信息(100字以内)..." /></div>  
  84.  </div> 
  85.  <div class="wenwen_help right"
  86.    <button onClick="send()" class="right">发送</button> 
  87.  </div> 
  88.  <div style="opacity:0;" class="clear"></div> 
  89. </div> 
  90.  
  91. <script type="text/javascript"
  92.  if ("WebSocket" in window){ 
  93.   var ws = new WebSocket("ws://192.168.0.1:9052"); 
  94.   ws.onopen = function(){ 
  95.    console.log("握手成功"); 
  96.    var myemail = $("#myemail").val(); 
  97.    var toemail = $("#toemail").val(); 
  98.    var arr = {"flag":"init","from":myemail,"to":toemail}; 
  99.    var str = JSON.stringify(arr); 
  100.    ws.send(str); 
  101.   }; 
  102.   ws.onmessage = function(e){ 
  103.    var toemail = $("#toemail").val(); 
  104.    var toavatar = $("#toavatar").val(); 
  105.    var obj = JSON.parse(e.data); 
  106.    console.log(e.data); 
  107.    //但同时与两个人聊天时,可能两个人的消息都会出现在当前窗口,所以此处加个判断,此窗口只接收当前聊天对象的消息,其他则忽略 
  108.    if(obj.from === toemail){ 
  109.     var ans = '<div class="answer"><div class="heard_img left"><img src="'+toavatar+'"></div>'
  110.      ans += '<div class="answer_text"><p>'+obj.content+'</p><i></i>'
  111.      ans += '</div></div>'
  112.      $('.speak_box').append(ans); 
  113.      for_bottom(); 
  114.    } 
  115.   }; 
  116.   ws.onerror = function(){ 
  117.    console.log("error"); 
  118.    var str = '<div class="question">'
  119.    str += '<div class="heard_img right"><img src="/static/images/xitong.jpg"></div>'
  120.    str += '<div class="question_text clear"><p>聊天服务器出现异常,暂时无法提供服务。</p><i></i>'
  121.    str += '</div></div>'
  122.    $('.speak_box').append(str); 
  123.    $('.write_box input').val(''); 
  124.    $('.write_box input').focus(); 
  125.    autoWidth(); 
  126.    for_bottom(); 
  127.   }; 
  128.  
  129.   function send() { 
  130.    var content = $('.write_box input').val(); 
  131.   if(content === ''){ 
  132.    alert('请输入消息!'); 
  133.    $('.write_box input').focus(); 
  134.   }else
  135.     var toemail = $("#toemail").val(); 
  136.     var myemail = $("#myemail").val(); 
  137.     var myavatar = $("#myavatar").val(); 
  138.     var arr = {"flag":"msg","to":toemail,"from":myemail,"content":content}; 
  139.     var msg = JSON.stringify(arr); 
  140.     console.log(msg); 
  141.     ws.send(msg);  
  142.     var str = '<div class="question">'
  143.     str += '<div class="heard_img right"><img src="'+myavatar+'"></div>'
  144.     str += '<div class="question_text clear"><p>'+content+'</p><i></i>'
  145.     str += '</div></div>'
  146.    $('.speak_box').append(str); 
  147.    $('.write_box input').val(''); 
  148.    $('.write_box input').focus(); 
  149.    autoWidth(); 
  150.    for_bottom(); 
  151.    } 
  152.     
  153.   } 
  154.  }else
  155.   alert("您的浏览器不支持 WebSocket!"); 
  156.  } 
  157.      
  158.  function for_bottom(){ 
  159.  var speak_height = $('.speak_box').height(); 
  160.  $('.speak_box,.speak_window').animate({scrollTop:speak_height},500); 
  161.  } 
  162.    
  163.  function autoWidth(){ 
  164.  $('.question_text').css('max-width',$('.question').width()-60); 
  165.  } 
  166.    
  167.  autoWidth(); 
  168.    
  169. </script> 
  170.  
  171. </body> 
  172. </html> 

数据表结构:

  1. CREATE TABLE `app_offline` ( 
  2.  `id` int(11) NOT NULL AUTO_INCREMENT, 
  3.  `fromvarchar(50) DEFAULT NULL COMMENT '离线发送方'
  4.  `tovarchar(50) DEFAULT NULL COMMENT '离线接收方'
  5.  `content` varchar(1000) DEFAULT NULL COMMENT '发送的离线内容'
  6.  `status` tinyint(4) DEFAULT '0' COMMENT '发送状态:0-未发送,1-已发送'
  7.  `addtime` int(11) DEFAULT NULL COMMENT '发送方发送时间'
  8.  PRIMARY KEY (`id`) 
  9. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 

具体效果:

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

Tags: swoole websocket redis

分享到: