前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PHP用swoole+websocket和redis实现web一对一聊天

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

作者头像
砸漏
发布2020-10-20 14:31:13
4900
发布2020-10-20 14:31:13
举报
文章被收录于专栏:恩蓝脚本恩蓝脚本

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

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

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

代码语言:javascript
复制
<?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();

客户端代码:

代码语言:javascript
复制
<!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 

数据表结构:

代码语言:javascript
复制
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

具体效果:

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档