PHP用swoole+websocket和redis实现web一对一聊天
吾爱主题
阅读:139
2021-09-16 15:42:00
评论:0
Redis 实现每个连接websocket的服务都唯一绑定一个用户。通过 用户账号 = websocket fd 存到redis中。
Mysql 实现离线消息池。如果一个用户不在线,则其他用户发送给他的消息暂时存储在mysql。待该用户上线时,再从离线消息池取出发送。
具体参考代码和相应注释:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | <?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(); |
客户端代码:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | <!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 > |
数据表结构:
?1 2 3 4 5 6 7 8 9 | 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 |
具体效果:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://segmentfault.com/a/1190000020911544
声明
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。