laravel 9.5.1 wokerman 搭建聊天室demo

1.安装基础代码

composer create-project laravel/laravel chatdemo
composer require wokerman/wokerman

2.

php artisan make:command WokermanServer

app\Console\Commands\WokermanServer.php

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\App;
use Workerman\Worker;

class WokermanServer extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'wokerman {action} {--daemonize}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'workerman 启动停止';

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        global $argv;//定义全局变量
        $arg = $this->argument('action');
        $argv[1] = $arg;
        $argv[2] = $this->option('daemonize') ? '-d' : '';//该参数是以daemon(守护进程)方式启动

        global $text_worker;
        // 创建一个Worker监听2345端口,使用websocket协议通讯
        $text_worker = new Worker("websocket://0.0.0.0:2345");
        $text_worker->uidConnections = array();//在线用户连接对象
        $text_worker->uidInfo = array();//在线用户的用户信息
        // 启动4个进程对外提供服务
        $text_worker->count = 4;
        //引用类文件
        $handler = App::make('Handler\WokermanHandler');
        $text_worker->onConnect = array($handler,"handle_connection");
        $text_worker->onMessage = array($handler,"handle_message");
        $text_worker->onClose = array($handler,"handle_close");
        $text_worker->onWorkerStart = array($handler,"handle_start");

        // 运行worker
        Worker::runAll();

        return 0;
    }
}


在app下新建Handler目录,然后新建 WokermanHandler.php

<?php
namespace Handler;

use Workerman\Lib\Timer;

class WokermanHandler
{
    private $heartbeat_time = 55;//心跳间隔55秒

    //当客户端连上来时分配uid,并保存链接,并通知所有客户端
    public function handle_connection($connection)
    {
        global $text_worker;

        //判断是否设置了UID
        if (!isset($connection->uid)) {
            //给用户分配一个UID
            $connection->uid = $this->random_string();
            //保存用户的uid
            $text_worker->uidConnections[$connection->uid] = $connection;
            //向用户返回创建成功的信息
            $connection->send("用户:[".$connection->uid."] 创建成功");
        }
    }

    public function handle_start()
    {
        global $text_worker;
        //每秒都判断客户端是否已下线
        Timer::add(1, function () use ($text_worker) {
            $time_now = time();
            foreach ($text_worker->connections as $connection) {
                // 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
                if (empty($connection->lastMessageTime)) {
                    $connection->lastMessageTime = $time_now;
                    continue;
                }
                // 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
                if ($time_now - $connection->lastMessageTime > $this->heartbeat_time) {
                    $connection->close();
                }
            }
        });
        //每隔30秒就向客户端发送一条心跳验证
        Timer::add(50, function () use ($text_worker) {
            foreach ($text_worker->connections as $conn) {
                $conn->send('{"type":"ping"}');
            }
        });
    }

    //当客户端发送消息过来时,转发给所有人
    public function handle_message($connection, $data)
    {
        global $text_worker;
        //debug
        //echo "data_info:".$data.PHP_EOL;
        $connection->lastMessageTime = time();
        $data_info=json_decode($data, true);
        if (!$data_info) {
            $connection->send("消息格式错误");
            return ;
        }
        //判断业务类型
        switch ($data_info['type']) {
            case 'login':
                //判断用户信息是否存在
                if (empty($data_info['user_id'])) {
                    $connection->send("{'type':'error','msg':'非法请求'}");
                    return $connection->close();
                }
                //判断用户是否已经登录了
                $user_ids=array_column($text_worker->uidInfo, "user_id");
                if (in_array($data_info['user_id'], $user_ids)) {
                    $connection->send("{'type':'error','msg':'你在其它地方已登录'}");
                    return $connection->close();
                }
                //存储用户信息
                $text_worker->uidInfo[$connection->uid]=array(
                    "user_id"=>$data_info['user_id'],
                    "user_name"=>htmlspecialchars($data_info['user_name']),
                    "create_time"=>date("Y-m-d H:i"),
                );
                //返回数据
                if (isset($data_info['to_uid']) && $data_info['to_uid'] == "all") {
                    $return_data=array(
                        "type"=>"login",
                        "uid"=>$connection->uid,
                        "user_name"=>htmlspecialchars($data_info['user_name']),
                        "send_time"=>date("Y-m-d H:i", time()),
                        "user_lists"=>$text_worker->uidInfo
                    );
                    $curral_data=array(
                        "type"=>"login_uid",
                        "uid"=>$connection->uid,
                    );
                    $connection->send(json_encode($curral_data));
                    //给所有用户发送一条数据
                    foreach ($text_worker->connections as $conn) {
                        $conn->send(json_encode($return_data));
                    }
                } else {
                    return ;
                }
                return;
            //用户发消息
            case 'say':
                if (!isset($text_worker->uidInfo[$connection->uid]) || empty($text_worker->uidInfo[$connection->uid])) {
                    $connection->send('{"type":"error","msg":"你已经掉线了"}');
                }
                //获取到当前用户的信息
                $user_info=$text_worker->uidInfo[$connection->uid];

                //判断是私聊还是群聊
                if ($data_info['to_uid'] != "all") {
                    //私聊
                    $return_data=array(
                        "type"=>"say",
                        "from_uid"=>$connection->uid,
                        "from_user_name"=>$user_info['user_name'],
                        "to_uid"=>$data_info['to_uid'],
                        "content"=>nl2br(htmlspecialchars($data_info['content'])),
                        "send_time"=>date("Y-m-d H:i")
                    );
                    if ($data_info['to_uid'] == $connection->uid) {
                        $connection->send(json_encode($return_data));
                        return;
                    }
                    //判断用户是否存在,并向对方发送数据
                    if (isset($text_worker->uidConnections["{$data_info['to_uid']}"])) {
                        $to_connection=$text_worker->uidConnections["{$data_info['to_uid']}"];
                        $to_connection->send(json_encode($return_data));
                    }
                    //向你自己发送一条数据
                    $connection->send(json_encode($return_data));
                } else {
                    //群聊
                    $return_data=array(
                        "type"=>"say",
                        "from_uid"=>$connection->uid,
                        "from_user_name"=>$user_info['user_name'],
                        "to_uid"=>"all",
                        "content"=>nl2br(htmlspecialchars($data_info['content'])),
                        "send_time"=>date("Y-m-d H:i")
                    );
                    //向所有用户发送数据
                    foreach ($text_worker->connections as $conn) {
                        $conn->send(json_encode($return_data));
                    }
                }
                return;
            case "pong":
                return;
        }
    }

    //当客户端断开时,广播给所有客户端
    public function handle_close($connection)
    {
        global $text_worker;
        $user_name=$text_worker->uidInfo[$connection->uid]['user_name'] ?? "";
        unset($text_worker->uidConnections[$connection->uid]);
        unset($text_worker->uidInfo[$connection->uid]);
        if (!empty($user_name)) {
            $return_data=array(
                "type"=>"logout",
                "uid"=>$connection->uid,
                "user_name"=>$user_name,
                "create_time"=>date("Y-m-d H:i:s"),
            );
            foreach ($text_worker->connections as $conn) {
                $conn->send(json_encode($return_data));
            }
        }
    }

    public function random_string()
    {
        return substr(uniqid('', true), 15).substr(microtime(), 2, 8);
    }
}



3.修改composer。json 增加handler映射

"autoload": {
    "psr-4": {
        "App\\": "app/",
        "Database\\Factories\\": "database/factories/",
        "Database\\Seeders\\": "database/seeders/",
        "Handler\\": "app/Handler/"
    }
},


 启动websocker服务 ,应该不会报错

composer update
php artisan wokerman start


成功的截图

image.png


4.写个html调用一下

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>测试websocket</title>
	</head>
	<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js" type="text/javascript" charset="utf-8"></script>
	<body>
		<div id="chat_list">
			<p></p>
		</div>
		<input type="text" value="" id="chat_msg">
		<button type="button" id="send">发送</button>
		<script type="text/javascript">
			var wsurl = "ws://127.0.0.1:2345";
			var ws = new WebSocket(wsurl);
			ws.onopen = function(){
				var params = {}
				params.type = 'login'
				params.user_id = Math.ceil(Math.random()*100)
				params.user_name = '测试用户'+params.user_id
				params.to_uid = ''
				ws.send(JSON.stringify(params))
				console.log('注册成功',JSON.stringify(params))
			}
			
			ws.onmessage = function(evt){
				console.log('返回的信息data',evt.data)
				if(msg = parseJSON(evt.data)){
					console.log('返回的信息msg',msg)
					if(msg.type == 'say'){
						$("#chat_list").append("<p>"+msg.content+"</p>")
					}
				}
			}
			
			ws.onclose = function(){
				console.log('关闭了')
			}
			$().ready(function(){
				$("#send").click(function(){
					var m = $("#chat_msg").val();
					if(m == ''){
						alert('消息不能为空')
						return false;
					}
					
					params = {}
					params.type = 'say'
					params.to_uid = 'all'
					params.content = m
					ws.send(JSON.stringify(params))
					console.log('发送了消息',JSON.stringify(params))
					$("#chat_msg").val('')
				})
			})
			
			
			function parseJSON(str) {
			    if (typeof str == 'string') {
			        try {
			            return JSON.parse(str);
			        } catch(e) {
			            return false;
			        }
			    }
			    console.log('It is not a string!')    
			}
		</script>
	</body>
</html>


image.png

打赏

看恩吧
网站不承担任何有关评论的责任
  • 最新评论
  • 总共条评论
取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦