您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)PHP如何使用swoole+websocket和redis實(shí)現(xiàn)web一對(duì)一聊天的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。
Redis 實(shí)現(xiàn)每個(gè)連接websocket的服務(wù)都唯一綁定一個(gè)用戶。通過 用戶賬號(hào) = websocket fd 存到redis中。
Mysql 實(shí)現(xiàn)離線消息池。如果一個(gè)用戶不在線,則其他用戶發(fā)送給他的消息暫時(shí)存儲(chǔ)在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í)候初始化,每個(gè)用戶登錄時(shí)記錄該用戶對(duì)應(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ā)送,一對(duì)一聊天,根據(jù)每個(gè)用戶對(duì)應(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}" /> <!-- 對(duì)話內(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="請(qǐng)輸入信息(100字以內(nèi))..." /></div> </div> <div class="wenwen_help right"> <button onClick="send()" class="right">發(fā)送</button> </div> <div 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í)與兩個(gè)人聊天時(shí),可能兩個(gè)人的消息都會(huì)出現(xiàn)在當(dāng)前窗口,所以此處加個(gè)判斷,此窗口只接收當(dāng)前聊天對(duì)象的消息,其他則忽略 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('請(qǐng)輸入消息!'); $('.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
具體效果:
感謝各位的閱讀!關(guān)于“PHP如何使用swoole+websocket和redis實(shí)現(xiàn)web一對(duì)一聊天”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。