基础介绍

(1)Swoole是一个面向生产环境的 PHP 异步网络通信引擎,使 PHP 开发人员可以编写高性能的异步并发 TCP、UDP、Unix Socket、HTTP,WebSocket 服务。Swoole 可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(IOT)、车联网、智能家居等领域。 使用 PHP + Swoole 作为网络通信框架,可以使企业 IT 研发团队的效率大大提升。—百度百科

(2)WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 —百度百科

(3)那这篇的主题就是如何使用Swoole+WebSocket实现一个简易的聊天室。

熟悉网络通信协议的同学肯定不会陌生。

功能需求及问题处理

web端:(1)每次刷新都会生成一个唯一的ID(id值从1开始).(2)第一次进入网站时会要求用户设置昵称并会与ID进行绑定。问题点:(1)刷新页面后用户标志(ID)会重新生成,之前生成ID被弃用。(2)WebSocket生成了新的用户ID,但是跟现在的无法形成关联关系。

server端:(1)当用户进入聊天室后,发送广播给所有人并加入聊天群组(使用redis存储)。(2)当用户退出直播间后,发送广播给所有人并清除该用户的记录。(3)用户每发送一次消息都要形成新的记录广播给所有人。(4)用户生成新的昵称后把昵称推送给他。

web端问题处理方法:(1)浏览器刷新时提醒用户刷新即将重新获得新的身份。(2)用户连接成功后记录用户name,每次连接把这个name带上,清除之前该name的绑定关系,形成新的关系。

php实现代码:

<?php
include
"RedisManager.php";
$server = new Swoole\WebSocket\Server("0.0.0.0",8877);
//客户端连接
$server->on('open', function (SwoolelWebSocketiServer $server, $request) {
   //连接成功把当前在线的用户返回
   $user_list = RedisConnectgetRedis()->hGetAll('message:user');
   alone($server, $request->fd,[
       'type'=>'first',
       'data'=>$user_list
   ]);
});

//接收客户端发送的消息
$server->on('message' , function (Swoole\WebSocket\Server $server, $frame){
   $message_data = json_decode($frame->data, true);
   $type = $message_data['type'];
   $data = $message_data['user_name'];
   if (isset($message_data['send_message'])){
       $user_message = $message_data['send_message'];
   }
   switch ($type) {
       case 'save_user':
           $new_name = '大夏单子'.$frame->fd.'';
           RedisConnect::getRedis()->hset('message:user',$frame->fd,$new_name);
           //把生成的用户昵称返回给他
           alone($server, $frame->fd,[
               'type' => 'new_name',
               'name' => $new_name,
               'id' => $frame->fd
           ]);
           //广播消息给其他用户
           groupSending($server, [
               'type' => 'open',
               'name' => $new_name,
               'id' => $frame->fd,
           ]);
           break;
       case 'send_message':
           $msg = [
               'id' => $frame-> fd,
               'user_name' => $data,
               'message' => $user_message,
               'type' => 'message'
           ];
           //接受用户发送的消息
           RedisConnect::getRedis()->lpush('message:user:say', $msg);
           groupSending($server, $msg);
           break;
   }
});
//退出聊天室
$server->on('close' , function ($ser, $fd){
   $user = RedisConnect::getRedis()->hget('message:user',$fd);
   RedisConnect::getRedis()->hdel('message:user', $fd);
   $msg = [
       'id' => $fd,
       'user_name' => $user,
       'message' => '退出聊天室',
       'type' => 'close'
   ];
   groupSending($ser, $msg, $fd);
});
//开启服务
$server->start();
//群发消息
function groupSending($server, $msg, $self = null){
   foreach ($server->connections as $conn){
       if ($conn == $self) break;
       //不再推送给当前退出的用户
       $server->push($conn, json_encode($msg));
   }
}
//单独发消息
function alone($server,$fd, $msg){
   $server->push($fd, json_encode($msg));
}

html代码:

<IDOCTYPE html>
    <html>
<head>
    <meta charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>聊天室</title>
    <style>
        *{margin:0;padding:0;).box{width:602px;height:500px;margin:20px
        auto}.body{width:500px;height:500px;border:1px solid #333;padding:20px;box-sizing:border-box;float:right}.title{text-align:center;margin-top:10px}.msg{margin-top:20px;width:100%;text-align:left;}.message_box{width:100%;height:90%;margin-top:10px;font-size:12px;overflow:hidden;overflow-y:auto;}.name{font-weight:bold;}.right{text-align:right;}.prompt{width:600px;margin:0
        auto}.live{width:100px;height:500px;float:left;border:1px solid #333;font-size:12px;text-align:center}.Iive-box{height:100%;overflow:hidden;overflow-y:auto}.Ilive-box p{margin-top:10px;}
    </style>
</head>
<body>
<div>
    <div>
    <div>+十十++十+十+++聊天室++十十+十++++++</div>
        <div id="test"></div>
    </div>
    <div>
    <div>在线人数</div>
        <div></div>
    </div>
</div>
<div>
    <input type="text" name="say">
    <input type="button" value="发送" id="send">
    <!--<input type="button" value="增加消息" id="add" > -->
</div>
</body>
<script src="jquery.js"></script>
<script src='//cdn.bootcss.com/socket.io/1.3.7/socket.io.js'></script>
<script src='https://cdn.bootcss.com/socket.io/2.0.3/socket.io.js'></script>
<script type="text/javascript">
    window.onbeforeunload = function (event){
        window.localStorage.clear();
    };
    $(function(){
        window.user_name = '';
        //获取在线用户
        if(!("WebSocket" in window)){
            alert("您的浏览器不支持WebSocketl");
            return false;
        }
        //连接
        let ws = new WebSocket("ws://49.235.172.110:8877");//连接成功
        ws.onopen = function (event){
            //修改昵称
            let user_info = {
                'type' : 'save_user',
                'user_name': '',
            };
            ws.send(JSON.stringify(user_info));
            console.log('连接成功','修改昵称成功');
        };
        ws.onmessage = function (evt) {
            let received_msg = JSON.parse(evt.data);
            let type = received_msg['type'];
            switch (type){
                case 'message':
                    add_msg(received_msg);
                    break;
                case 'close':
                    $(".Jive-box").find('p[user='+ received_msg['id'] + ']').remove();
                    break;
                case 'connect':
                    console.log(received _msg);
                    break;
                case 'new_name':
                    user_name = received_msg[ 'name'];
                    break;
                case 'open':
                    $('.live-box').append('<p user="' + received_msg['id']+'">'+
                    received_msg['name'] + '</p>');
                    break;
                case 'first':
                    let data = received_msg['data'];let str = ";
                    $.each(data, function (k, v){
                        if (isNaN(v)) {
                            str += '<p user="' + k + '">' + v + '</p>';
                        }
                    })
                    $('.live-box').append(str);
                    break;
            }
        }
        //发送消息
        $("#send").click(function () {
            send_msg();
        })
        $("input[name=say]").bind("keydown", function (e){
            var theEvent = e || window.event;
            var code = theEvent.keyCode || theEvent.which || theEvent.charCode;
            if (code == 13) {
                send_msg();
            }
        });
        //发送消息
        function send_msg() {
            var message = $(input[name=say]).val();
            if (message !== '' ){
                var arr = {
                    'type': 'send_message',
                    'user_ name': user_name,
                    'send_message': message
                }
            }
            ws.send(JSON.stringify(arr));
            $('input[name=say]').val('');
            var ele = document.getElementById("test");
            if (ele.scrollHeight > ele.clientHeight) {
                ele.scrollTop = ele.scrollHeight + 100;
            }
        }
        //增加消息
        function add_msg(data){
            let str ='<div>';
            str += '<span>'+ data['user_name'] + '</span>:';
            str += '<span>'+ data['message'] + '</span> </div>';
            $('.message_box').append(str);
        }
    })
</script>
</html>

这篇文章只是简单的介绍前后端如何实现通信,很多的细节问题没有进行处理。UI比较low,可以按照自己需求进行修改…