Using swoole+websocket and redis to realize web one-to-one chat

Keywords: PHP Redis SQL MySQL JSON

Redis implements that each service connected to websocket is bound to a unique user. Save to redis through user account = websocket fd.

mysql implements the offline message pool. If a user is not online, messages sent to him by other users are temporarily stored in mysql. When the user is online, it is taken out from the offline message pool and sent.

Specific reference code and corresponding notes:

<?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 is the client id
});

$server->on('message', function (swoole_websocket_server $server, $frame) {
    $data = json_decode($frame->data,true); 
    if($data['flag'] == 'init'){
        //Initialize when the user is just connected, and record the corresponding fd of each user when logging in
        $GLOBALS['redis']->set($data['from'], $frame->fd);
        //Process offline messages to the user
        $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);
            }
            //Set message in message pool as sent
            $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'){
        //Uninitialized message sending, one-to-one chat, according to the corresponding fd of each user to specific users
        $tofd = $GLOBALS['redis']->get($data['to']); //Who should I send the message to
        $fds = []; //All online users (users who open chat windows)
        foreach($server->connections as $fd){
            array_push($fds, $fd);
        }
        if(in_array($tofd,$fds)){
            $tmp['from'] = $data['from']; //Who is the source of the news
            $tmp['content']  = $data['content']; //Message content
            $re = json_encode($tmp);
            $server->push($tofd , $re);
        }else{
            //The player is not online (not in the chat room) and sends information to the offline message pool
            $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 group chat
        
    }else if($data['flag'] == 'all'){
        //Total station broadcasting
        foreach($server->connections as $fd){
            $server->push($fd , $data);
        }
    }  
});

$server->on('close', function ($ser, $fd) {
    echo "client {$fd} closed\n";
});

$server->start();

Client code:

<!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);"></a></i>
                <div class="title">
                        <span class="title_d"><p>And {$tonickname} Chat</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}" />

<!-- Dialogue content -->
<div class="speak_window">
    <div class="speak_box">

    </div>
</div>
<!-- Content input-->
<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="Please enter information(100 Within word)..." /></div>   
    </div>
    <div class="wenwen_help right">
            <button onClick="send()" class="right">Send out</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("Handshake success");
            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);
            //However, when chatting with two people at the same time, the messages of both people may appear in the current window, so add a judgment here, this window only receives messages from the current chat object, and ignore other messages
            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>There is an exception in the chat server, and the service is temporarily unavailable.</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('Please input message!');
            $('.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("Your browser does not support 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>

Data table structure:

CREATE TABLE `app_offline` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `from` varchar(50) DEFAULT NULL COMMENT 'Offline sender',
  `to` varchar(50) DEFAULT NULL COMMENT 'Offline receiver',
  `content` varchar(1000) DEFAULT NULL COMMENT 'Offline content sent',
  `status` tinyint(4) DEFAULT '0' COMMENT 'Send status: 0-Unsent,1-has been sent',
  `addtime` int(11) DEFAULT NULL COMMENT 'Sender sending time',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

Specific effect:

phper always encounters some problems and bottlenecks in its advanced stage. There is no sense of direction when it writes more business code. I don't know where to start to improve. For this, I collated some data, including but not limited to: distributed architecture, high scalability, high performance, high concurrency, server performance tuning, TP6, laravel, YII2, Redis, Swoole, Swoft, Kafka, Mysql optimization, shell Script, Docker, microservice, Nginx and other advanced dry goods can be shared for free Please poke here.

Posted by Eal on Tue, 05 Nov 2019 06:13:55 -0800