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


Posted in PHP onNovember 05, 2019

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一对一聊天

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

PHP 相关文章推荐
php修改时间格式的代码
May 29 PHP
PHP中usort在值相同时改变原始位置问题的解决方法
Nov 27 PHP
PHP数组及条件,循环语句学习
Nov 11 PHP
深入for,while,foreach遍历时间比较的详解
Jun 08 PHP
php对称加密算法示例
May 07 PHP
PHP中多维数组的foreach遍历示例
Jun 13 PHP
非常实用的PHP常用函数汇总
Dec 17 PHP
PHP strtotime函数用法、实现原理和源码分析
Feb 04 PHP
PHP查询快递信息的方法
Mar 07 PHP
PHP 9 大缓存技术总结
Sep 17 PHP
mac下多个php版本快速切换的方法
Oct 09 PHP
PHP+MySQL实现模糊查询员工信息功能示例
Jun 01 PHP
基于thinkphp6.0的success、error实现方法
Nov 05 #PHP
php实现JWT(json web token)鉴权实例详解
Nov 05 #PHP
详解Laravel服务容器的绑定与解析
Nov 05 #PHP
php+laravel依赖注入知识点总结
Nov 04 #PHP
PHP保存Base64图片base64_decode的问题整理
Nov 04 #PHP
详解laravel passport OAuth2.0的4种模式
Nov 04 #PHP
laravel返回统一格式错误码问题
Nov 04 #PHP
You might like
献给php初学者(入门学习经验谈)
2010/10/12 PHP
thinkPHP模板算术运算相关函数用法分析
2016/07/12 PHP
JavaScript中yield实用简洁实现方式
2010/06/12 Javascript
JavaScript单元测试ABC
2012/04/12 Javascript
JS验证控制输入中英文字节长度(input、textarea等)具体实例
2013/06/21 Javascript
js浏览器本地存储store.js介绍及应用
2014/05/13 Javascript
CSS javascript 结合实现悬浮固定菜单效果
2015/08/23 Javascript
AngularJS手动表单验证
2016/02/01 Javascript
jQuery+CSS3实现点赞功能
2017/03/13 Javascript
jQuery插件HighCharts绘制2D饼图效果示例【附demo源码下载】
2017/03/21 jQuery
JavaScript数组排序reverse()和sort()方法详解
2017/12/24 Javascript
详解webpack模块化管理和打包工具
2018/04/21 Javascript
vue源码解析之事件机制原理
2018/04/21 Javascript
浅谈vue后台管理系统权限控制思考与实践
2018/12/19 Javascript
Vue+Element UI+Lumen实现通用表格分页功能
2019/02/02 Javascript
d3.js实现图形拖拽
2019/12/19 Javascript
小程序接入腾讯位置服务的详细流程
2020/03/03 Javascript
将Python代码嵌入C++程序进行编写的实例
2015/07/31 Python
Python模拟鼠标点击实现方法(将通过实例自动化模拟在360浏览器中自动搜索python)
2017/08/23 Python
python中enumerate() 与zip()函数的使用比较实例分析
2019/09/03 Python
Python属性和内建属性实例解析
2020/01/14 Python
django之导入并执行自定义的函数模块图解
2020/04/01 Python
python是怎么被发明的
2020/06/15 Python
Python创建简单的神经网络实例讲解
2021/01/04 Python
python快速安装OpenCV的步骤记录
2021/02/22 Python
css3 自定义字体font-face使用介绍
2014/05/14 HTML / CSS
打印机墨盒:123Inkjets
2017/02/16 全球购物
理肤泉俄罗斯官网:La Roche-Posay俄罗斯
2018/07/24 全球购物
Java里面如何把一个Array数组转换成Collection, List
2013/07/26 面试题
校园摄影活动策划方案
2014/02/05 职场文书
关工委先进个人事迹材料
2014/05/23 职场文书
2014教师“四风问题”对照检查材料思想汇报
2014/09/16 职场文书
教师年度个人总结
2015/02/11 职场文书
2016年母亲节广告语
2016/01/28 职场文书
oracle DGMGRL ORA-16603报错的解决方法(DG Broker)
2021/04/06 Oracle
使用CSS设置滚动条样式
2022/01/18 HTML / CSS