swoole + 浏览器webSocket 实现的聊天室

这两天在学习swoole,作为练手弄了个swoole + 浏览器webSocket 实现的聊天室,效果如下:

环境:php7.3 swoole4.4.3  hredis

参考代码下载地址 https://download.csdn.net/download/shenymce/11565378


 

WebScketServer.php文件

<?php
namespace App\swoole;


use App\swoole\hander\SockectHander;
use Swoole\Coroutine\Redis;

class WebSocketServer
{
    private static $server;
    private static $instance;
    private function __construct($port = 9517){
        self::$server = new \swoole_websocket_server("0.0.0.0",$port);
        self::$server->on("open",[$this,"onOpen"]);
        self::$server->on("message",[$this,"onMessage"]);
        self::$server->on("managerStop",[$this,"onManagerStop"]);
        self::$server->on("close",[$this,"onClose"]);
    }

    public static function getInstance($port = 9517){
        if(!self::$instance instanceof self){
            self::$instance = new self($port);
        }
        return self::$instance;
    }

    public function onOpen($server, $request){
    }

    public function onMessage($server, $frame){
        $frame->data;
        $data = json_decode($frame->data,true); // 处理接收到的服务器数据
        if(is_array($data)){
            // var_dump($data);
            // 实现热加载,不需要重启服务器就可以加载 SockectHander 中修改的代码
            if(isset($data["debug"]) && $data["debug"]){
                self::$server->reload();
                // var_dump("set debug !!");
                // 服务器重新加载会耗时,因此等待3秒
                sleep(3);
            }
            $sendData = compact("server", "data");
            //var_dump($sendData);
            if(method_exists(new SockectHander(),$data["cmd"])){
                call_user_func([new SockectHander(),$data["cmd"]], $frame->fd, $sendData);
            }
        }else{
            // 用户设置昵称是输出格式错误处理
            self::$server->push($frame->fd,json_decode(["msg"=>"参数格式错误!","errCode"=>"-1"]));
        }
    }

    public function onClose($server, $fd){
        // 断开连接处理redis
        go(function () use ($fd){
            echo $fd."closed\n";
            $redis = new Redis();
            $redis->connect("127.0.0.1", 6379);
            $userlist = json_decode($redis->get("userlist"),true);
            $username = isset($userlist[$fd])?$userlist[$fd]:"";
            unset($userlist[$fd]);
            if($username){
                foreach ($userlist as $k => $item) {
                    if(self::$server->connection_info($k)) {
                        self::$server->push($k, json_encode(["msg" => "用户 <b style='color: red;padding: 0 5px;'>" . $username . "</b> 离开聊天室!", "userlist" => $userlist]));
                    }else{
                        unset($userlist[$k]); // 清空已下线用户
                    }
                }
            }
            $redis->set("userlist",json_encode($userlist));
        });
    }

    public function onManagerStop($server){
        // 清空所有用户信息
        $this->clearUserlist();
    }

    public function clearUserlist(){
        // 清空所有用户信息
        go(function (){
            $redis = new Redis();
            $redis->connect("127.0.0.1", 6379);
            $redis->del("userlist");
        });
    }

    public function start(){
        self::$server->start();
    }
}

 

SockectHander.php文件

 

<?php
namespace App\swoole\hander;

use Swoole\Coroutine\Redis;

class SockectHander
{
    // 用户设置昵称
    public function setName($fd, $data){

        if(isset($data["data"]["data"]["name"]) && $username = $data["data"]["data"]["name"]){
            go(function () use ($data, $fd, $username){ // 使用协程方便操作redis
                $server = $data["server"];
                $redis = new Redis();
                $redis->connect("127.0.0.1", 6379);

                // 获得已登录的用户列表
                $userlist = json_decode($redis->get("userlist"), true)?:[];
                if(in_array($username, $userlist)){
                    $server->push($fd,json_encode(["msg"=>"用户昵称已经存在,昵称设置失败!","errCode"=>"2"]));
                }else{

                    // 添加用户到已登录列表
                    $userlist[$fd] = $username;

                    // 便利发送数据到所有用户
                    foreach ($userlist as $k => $v) {

                        // 判断是不是自己发的消息。并获得自己的fd
                        $isMe = false;
                        if($fd == $k){
                            $isMe = $fd;
                        }

                        // 判断用户状态,只发送给在线用户
                        if($server->connection_info($k)){
                            $server->push($k, json_encode(["msg"=>"欢迎用户 <b style='color: red;padding: 0 5px;'>".$username."</b> 加入聊天室!","userlist"=>$userlist,"isMe"=>$isMe]));
                        }else{
                            unset($userlist[$k]); // 清空已下线用户
                        }
                    }
                    $redis->set("userlist", json_encode($userlist));
                }
            });
        }else{

            // 用户设置昵称是输出格式错误处理
            $data["server"]->push($fd,json_encode(["msg"=>"参数格式错误,用户昵称设置失败!","errCode"=>"1"]));
        }
    }

    // 用户发送数据进行聊天
    public function sendMsg($fd, $data){

        if(isset($data["data"]["data"]["to"]) && $to = $data["data"]["data"]["to"]){ // 私聊
            $server = $data["server"];
            $msg = $data["data"]["data"]["msg"];

            $redis = new Redis();
            $redis->connect("127.0.0.1", 6379);

            // 获得已登录的用户列表
            $userlist = json_decode($redis->get("userlist"), true);

            /**
             * 参数说明
             * msg 消息内容
             * from 发送人
            **/
            $sendData = ["msg" => $msg,"from"=>$userlist[$fd]];
            $status = $server->connection_info($to);
            if($status){
                // 发送消息给指定用户
                $server->push($to, json_encode($sendData));
            }else{
                $sendData1["msg"] = "指定的用户已下线";
                $sendData1["errCode"] = 9;
                $server->push($fd, json_encode($sendData1));
            }


        }else{ // 发送给所有人
            go(function () use ($data,$fd) { // 使用协程方便操作redis
                $server = $data["server"];
                $redis = new Redis();
                $redis->connect("127.0.0.1", 6379);

                // 获得已登录的用户列表
                $userlist = json_decode($redis->get("userlist"), true);

                $msg = $data["data"]["data"]["msg"];

                /**
                 * 参数说明
                 * msg 消息内容
                 * from 发送人
                 **/
                $sendData = ["msg" => $msg,"from"=>$userlist[$fd]];

                // 便利发送数据到所有用户
                foreach ($userlist as $k => $v) {
                    // 发送给非自己之外的所有人
                    if($fd != $k){
                        if($server->connection_info($k)) {
                            $server->push($k, json_encode($sendData));
                        }else{
                            unset($userlist[$k]); // 清空已下线用户
                        }
                    }
                }
                $redis->set("userlist", json_encode($userlist));
            });
        }
    }
}

 

服务器调用页面主要代码

 

$obj = \App\swoole\WebSocketServer::getInstance();

$obj->start();

 

sokect.html文件

<!DOCTYPE html>
<html>
<head lang="en">
   <meta charset="UTF-8">
   <title id="myTitle">swoole + webSocket 实现的在线聊天室</title>
</head>
<style rel="stylesheet" type="text/css">
   body {background-color: #eeeeee;}
   .master{min-width: 400px;}
   h1 {font-size: 25px;color: #750e0e;}
   #myname, #text {width: 80%;height: 35px;padding: 5px 10px;box-sizing: border-box;border-radius: 5px;font-weight: bold;margin: 0;border: 0;}
   .sendButton {width: 19%;line-height: 35px;box-sizing: border-box;background-color: #c41b1b;margin: 0;color: #fff;border: 0;border-radius: 5px;}
   #setName, #sendMsg {background-color: #2d1d2d;line-height: 50px;box-sizing: border-box;padding: 15px;white-space: nowrap;}
   .show_send {box-sizing: border-box;margin: 10px auto;}
   .flex{display: flex;}
   #msg {flex: 8;height: 280px;padding: 5px 10px;box-sizing: border-box;border-radius: 5px;font-weight: bold;background-color: #fff;overflow-y: auto;padding-bottom: 20px;}
   .users{flex: 2;background-color: #153f48;}
   .userlist {list-style: none;padding:0;line-height: 35px;box-sizing: border-box;margin: 0;color: #fff;border: 0;border-radius: 5px;overflow-y: auto;height: 239px;}
   .userlist li{cursor: pointer;box-sizing: border-box;padding: 2px 10px;white-space: nowrap;overflow: hidden;border-bottom: 1px solid;}
   .userlist .item:hover, .userlist .item.active{background-color: #333;}
   .users .title{background-color: #213162;height: 40px;border-bottom:1px solid;border-bottom: 1px solid; color: #fff;line-height: 40px;padding: 0 10px;}
   .footer{position: fixed;width: 100%;bottom: 0;padding: 20px;background-color: #fff;font-size: 14px;font-weight: bold;left: 0;}

</style>
<body>
<div class="master">
   <h1>swoole + webSocket 实现的在线聊天室</h1>
   <!--发送信息-->
   <div id="sendMsg" class="main box-shadow" style="display:none;">
      <div class="flex">
         <div id="msg"></div>
         <div class="users">
            <div class="title">在线用户</div>
            <ul class="userlist"></ul>
         </div>
      </div>
      <div class="show_send">
         <input title="msg" type="text" id="text">
         <input class="sendButton sendMsg" value="发送消息" type="submit">
      </div>
   </div>
   <!--设置昵称-->
   <div id="setName" class="box-shadow">
      <input title="username" type="text" id="myname"/>
      <input type="submit" class="setUserName sendButton" value="设置昵称">
   </div>
</div>
<div class="footer">
   操作说明:<br>
   1、聊天室内用户昵称不可重名。<br>
   2、用户昵称设置成功后进入聊天界面,右侧将显示所有在线用户。<br>
   3、单击右侧用户可进行私聊,发送的消息只私聊用户可见。<br>
   4、双击私聊用户退出私聊,发送的消息全局可见。<br>
   5、代码提供 <a href="mailto:shenymce@qq.com">shenymce</a>
</div>
</body>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script type="text/javascript">
   var wsServer = 'ws://192.168.2.100:9517';
   var toFd = 0;
   var toName = "";
   var myfd = 0;
   var socket = new WebSocket(wsServer);
   
   // 连接服务器成功
   socket.onopen = function (evt) {
      console.log(socket.readyState);
   };
   
   // 格式化当期日期成 2019-01-01 01:01:01
   function nowData() {
      var d = new Date();
      return d.getFullYear()+"-"+(d.getMonth()+1)+"-"+d.getDate()+" "+d.getHours()+":"+d.getMinutes()+":"+d.getSeconds();
   }

   // 接收到服务器消息
   socket.onmessage = function (evt) {
      var data = JSON.parse(evt.data);
      console.log(data);
      var html = "";
      if(data.isMe){
         myfd = data.isMe;
      }
      if(data.userlist){
         for(var key in data.userlist){
            // 右侧不显示用户自己
            if(myfd != key){
               html +="<li class='item' data-id='"+key+"'>"+data.userlist[key]+"</li>";
            }
         }
         var objUserlist = $(".userlist");
         objUserlist.html(html).find(".item").click(function () {
            $(".userlist .item").removeClass("active");
            $(this).addClass("active");
            toFd = $(this).data("id");
            toName = $(this).text();
         });
         objUserlist.find(".item").dblclick(function () {
            $(this).removeClass("active");
            toFd = 0;
            toName = "";
         });
      }
      if(data.from){
         formatMsg(data.msg,"r",data.from);
      }else{
         formatMsg(data.msg,"r");
      }

      if(data.errCode == 2){
         setTimeout(function () {
            $("#myname").val("");
            $("#setName").show();
            $("#sendMsg").hide();
         },3000)
      }
   };

   socket.onclose = function (evt) {
      alert("连接服务器失败!");
   };

   socket.onerror = function (evt, e) {
      alert('错误:' + evt.data);
   };

   function formatMsg(msg,flag,from) {
      var html = "<div style='font-size: 16px;'>";
      var align = "left";
      var color = "green";
      if(flag == "right" || flag == "r"){
         align = "right";
         color = "blue";
      }
      var fromUser = "";
      if(from){
         fromUser  = "<b style='color: red;padding: 0 10px;'> "+from+" </b>"
      }
      var toStr = "";
      if(toName && !from){
         toStr  = "<b style='color: red;padding: 0 10px;'> "+toName+" </b>"
      }
      html += "<div style='height:25px;color:"+color+"; text-align: "+align+"'>" + fromUser + nowData() + toStr + "</div>";
      html += "<div style='height:25px;color:#000; text-align: "+align+"'>"+msg+"</div>";
      html += "</div>";
      var obj = $("#msg");
      var omsg = obj.html();
      obj.html(omsg + html).scrollTop(100000000000000);// 自动滚动到最底部
   }

   $(function () {
      $("#myname").keydown(function (e) {
         if(e.keyCode == 13){
            $(".setUserName").click();
         }
      });

      $("#text").keydown(function (e) {
         if(e.keyCode == 13){
            $(".sendMsg").click();
         }
      });

      // 设置用户名称,登录到聊天室
      $(".sendMsg").click(function () {
         var msg = $("#text").val();
         if (!msg) {
            return false;
         }

         /**
          * 数据说明
          * cmd 服务器要调用的方法名
          * debug 是否进行热更新,服务器端hander有更新是设置成 1  可选参数
          * data 服务器要处理的数据
          * @type {string}
          */
         var data = {
            cmd: "sendMsg",
            debug: 0,
            data: {
               msg: msg
            }
         };
         if(toFd > 0){
            data.data.to = toFd;
         }
         socket.send(JSON.stringify(data));
         formatMsg(msg);
         $("#text").val("");
      });


      // 设置用户名称,登录到聊天室
      $(".setUserName").click(function () {
         var username = $("#myname").val();
         if (!username) {
            alert("请先设置名称!");
            return false;
         }
         $("#myTitle").html(username);

         /**
          * 数据说明
          * cmd 服务器要调用的方法名
          * debug 是否进行热更新,服务器端hander有更新是设置成 1  可选参数
          * data 服务器要处理的数据
          * @type {string}
          */
         var data = {cmd: "setName",debug: 0,data: {name: username}};
         socket.send(JSON.stringify(data));
         formatMsg("用户设置昵称!");
         formatMsg("等待服务器回应!");
         $("#setName").hide();
         $("#sendMsg").show();
      });
   });
</script>
</html>参考代码下载地址 https://download.csdn.net/download/shenymce/11565378

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值