Redis 實(shí)現(xiàn)每個連接websocket的服務(wù)都唯一綁定一個用戶。通過 用戶賬號 = websocket fd 存到redis中。
Mysql 實(shí)現(xiàn)離線消息池。如果一個用戶不在線,則其他用戶發(fā)送給他的消息暫時(shí)存儲在mysql。待該用戶上線時(shí),再從離線消息池取出發(fā)送。
具體參考代碼和相應(yīng)注釋:
?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'){
//用戶剛連接的時(shí)候初始化,每個用戶登錄時(shí)記錄該用戶對應(yīng)的fd
$GLOBALS['redis']->set($data['from'], $frame->fd);
//處理發(fā)給該用戶的離線消息
$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);
}
//設(shè)置消息池中的消息為已發(fā)送
$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'){
//非初始化的信息發(fā)送,一對一聊天,根據(jù)每個用戶對應(yīng)的fd發(fā)給特定用戶
$tofd = $GLOBALS['redis']->get($data['to']); //消息要發(fā)給誰
$fds = []; //所有在線的用戶(打開聊天窗口的用戶)
foreach($server->connections as $fd){
array_push($fds, $fd);
}
if(in_array($tofd,$fds)){
$tmp['from'] = $data['from']; //消息來自于誰
$tmp['content'] = $data['content']; //消息內(nèi)容
$re = json_encode($tmp);
$server->push($tofd , $re);
}else{
//該玩家不在線(不在聊天室內(nèi)),將信息發(fā)送到離線消息池
$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}" />
!-- 對話內(nèi)容 -->
div class="speak_window">
div class="speak_box">
/div>
/div>
!-- 內(nèi)容輸入-->
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字以內(nèi))..." />/div>
/div>
div class="wenwen_help right">
button onClick="send()" class="right">發(fā)送/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);
//但同時(shí)與兩個人聊天時(shí),可能兩個人的消息都會出現(xiàn)在當(dāng)前窗口,所以此處加個判斷,此窗口只接收當(dāng)前聊天對象的消息,其他則忽略
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>聊天服務(wù)器出現(xiàn)異常,暫時(shí)無法提供服務(wù)。/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>
數(shù)據(jù)表結(jié)構(gòu):
CREATE TABLE `app_offline` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`from` varchar(50) DEFAULT NULL COMMENT '離線發(fā)送方',
`to` varchar(50) DEFAULT NULL COMMENT '離線接收方',
`content` varchar(1000) DEFAULT NULL COMMENT '發(fā)送的離線內(nèi)容',
`status` tinyint(4) DEFAULT '0' COMMENT '發(fā)送狀態(tài):0-未發(fā)送,1-已發(fā)送',
`addtime` int(11) DEFAULT NULL COMMENT '發(fā)送方發(fā)送時(shí)間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
具體效果:
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
您可能感興趣的文章:- windows系統(tǒng)php環(huán)境安裝swoole具體步驟
- php使用Swoole實(shí)現(xiàn)毫秒級定時(shí)任務(wù)的方法
- php使用goto實(shí)現(xiàn)自動重啟swoole、reactphp、workerman服務(wù)的代碼
- PHP Swoole異步讀取、寫入文件操作示例
- PHP之Swoole學(xué)習(xí)安裝教程