PHP实现Workerman实例 高性能PHP Socket即时通讯框架

简介: PHP实现Workerman实例 高性能PHP Socket即时通讯框架

安装

首先通过 composer 安装

composer require topthink/think-worker

使用

SocketServer

在命令行启动服务端(需要2.0.5+版本)

php think worker:server

默认会在0.0.0.0:2345开启一个websocket服务。

如果需要自定义参数,可以在config/worker_server.php中进行配置,包括:

配置参数

描述

protocol

协议

host

监听地址

port

监听端口

socket

完整的socket地址

并且支持workerman所有的参数(包括全局静态参数)。

也支持使用闭包方式定义相关事件回调。

return [
  'socket'  =>  'http://127.0.0.1:8000',
  'name'    =>  'thinkphp',
  'count'   =>  4,
  'onMessage' =>  function($connection, $data) {
    $connection->send(json_encode($data));
  },
];

复制

也支持使用自定义类作为Worker服务入口文件类。例如,我们可以创建一个服务类(必须要继承 think\worker\Server),然后设置属性和添加回调方法

然后在worker_server.php中增加配置参数:

return [
  'worker_class'  =>  'app\http\Workerman',
];

app\http\Workerman.php

<?php
namespace app\http;
use think\Log;
use think\worker\Server;
use Workerman\Lib\Timer;
class Worker extends Server
{
    // socket 端口
    protected $worker;
    protected $processes = 1; // 一个进程数
    protected $socket = '';
    protected $uidConnections = []; // 用户连接
    protected $heartbeat_time = 55; // 心跳间隔检测
    protected $send_heartbeat_time = 10; // 发送心跳间隔
    protected $time_countdown = 0; // 计算时间
    public function __construct()
    {
        $this->socket = 'http://0.0.0.0:2345';
        parent::__construct();
    }
    /**
     * 收到信息
     */
    public function onMessage($connection, $data)
    {
        $connection->lastMessageTime = time();
        $connection->send(json_encode($data));
    }
    /**
     * 当连接建立时触发的回调函数
     */
    public function onConnect($connection)
    {
    }
    /**
     * 当连接断开时触发的回调函数
     */
    public function onClose($connection)
    {
        // 删除
        if (isset($connection->uid) && isset($this->uidConnections[$connection->uid])) {
            //unset($this->uidConnections[$connection->uid][$connection->id]);
            unset($this->uidConnections[$connection->uid]);
        }
    }
    /**
     * 当客户端的连接上发生错误时触发
     */
    public function onError($connection, $code, $msg)
    {
        ////echo "error $code $msg\n";
    }
    /**
     * 发送给所有人
     */
    private function sendAll($data, $flg = false, $type = '')
    {
        $msg = json_encode([
            'event' => '#publish',
            'data' => $data,
            'type' => $type
        ]);
        $connection_check = [];
        // 发送给登录的用户
        // foreach ($this->uidConnections as $connection) {
        //     $end = current($connection);
        //     $end->send($msg);
        //     $connection_check[] = $end->id;
        // }
        // 发送给所有人
        foreach ($this->worker->connections as $connection) {
            if (in_array($connection->id, $connection_check)) {
                continue;
            }
            $connection->send($msg);
        }
    }
    /**
     * 发送给指定用户
     */
    private function sendMsg($uid, $data, $type)
    {
        $msg = json_encode([
            'type' => $type,  //待处理todo 多种type消息
            'data' => $data,
            'alert' => true,
        ]);
        $uid = intval($uid);
        // 判断是否有在线的连接
//        if (isset($this->uidConnections[$uid])) {
//            $end = $this->uidConnections[$uid];
//            $end->send($msg);
//        }
        // 判断是否有在线的连接
        if (isset($this->uidConnections[$uid]) && $this->uidConnections[$uid]) {
            //$end = end($this->uidConnections[$uid]);
            //$end->send($msg);
            foreach ($this->uidConnections[$uid] as $connection) {
                $connection->send($msg);
            }
        }
    }
    /**
     * 每个进程启动
     * @param $worker
     */
    public function onWorkerStart($worker)
    {
        //每秒执行
        Timer::add(1, function () use ($worker) {
            $this->time_countdown++;
            $time_now = time();
            foreach ($worker->connections as $connection) {
                $connection->send('time_countdown:' . $this->time_countdown);
                // 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
                // 统一下发心跳
                if ($this->time_countdown % $this->send_heartbeat_time == 0) {
                    $connection->send(json_encode([
                        'type' => 'hello time',
                    ]));
                }
                if (empty($connection->lastMessageTime)) {
                    $connection->lastMessageTime = $time_now;
                    continue;
                }
                // 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
                if ($time_now - $connection->lastMessageTime > $this->heartbeat_time) {
                    $connection->close();
                }
            }
        });
    }
}

关于上传文件

当按照默认的worker做http服务器时,并不能直接使用request()->file('image')来获得上传的文件,具体可以参考workerman的上传文件第6点.因此只能迂回的使用Filesystem.无论怎样,不影响其getMime()等方法的正确性.

// $file = request()->file('image');
$file_data  =  $_FILES[0]['file_data'];
//$tmp_file  = tempnam('','tm_');  这种写法最终保存时扩展名为.tmp
$tmp_file  = sys_get_temp_dir().'/'.uniqid().'.'.explode('/',$_FILES[0]['file_type'])[1];
file_put_contents($tmp_file,$file);
$file  =  new  File($tmp_file);
$savename  =  Filesystem::putFile('upload',$file);
echo  $savename;

自定义workerman指令

有时候我们希望使用think的命令行运行workerman,这里做一个介绍,

1:先新建一个指令,参考文档:自定义指令,比如新建命令:

php think make:command Hello hello

2:复制下面的代码到指令里,覆盖原始的configureexecute方法

protected function configure()
  {
    // 指令配置
    $this->setName('convert')
      ->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status|connections", 'start')
      ->addOption('mode', 'm', Option::VALUE_OPTIONAL, 'Run the workerman server in daemon mode.')
      ->setDescription('the workerman command');
  }
  protected function execute(Input $input, Output $output)
  {
    // 指令输出
    $output->writeln('convert start');
    $action = $input->getArgument('action');
    $mode = $input->getOption('mode');
    // 重新构造命令行参数,以便兼容workerman的命令
    global $argv;
    $argv = [];
    array_unshift($argv, 'think', $action);
    if ($mode == 'd') {
      $argv[] = '-d';
    } else if ($mode == 'g') {
      $argv[] = '-g';
    }
    // 在这里放心的实例化worker,
    // 就像参照workerman文档写一样,
    // 无非在workerman的文档里,代码是新建纯php文件,但在这里,写到了一个方法里.
    $worker_1 = new Worker();
    $worker_2 = new Worker();
    Worker::runAll();
  }

3:运行的时候,使用如下命令:

//临时运行
php think hello start
//后台运行
php think hello start --mode d


目录
相关文章
|
1月前
|
SQL 安全 PHP
PHP 自发布以来一直在 Web 开发领域占据重要地位,PHP 8 更是带来了属性、刚性类型等新特性。
【10月更文挑战第1天】PHP 自问世以来,凭借其易用性和灵活性,在 Web 开发领域迅速崛起。从简单的网页脚本语言逐步演进为支持面向对象编程的现代语言,尤其自 PHP 5.3 引入命名空间后,代码组织和维护变得更加高效。PHP 7 的性能优化和 PHP 8 的新特性(如属性和刚性类型)进一步巩固了其地位。框架如 Laravel、Symfony、Yii2 和 CodeIgniter 等简化了开发流程,提高了效率和安全性。
45 2
|
11天前
|
API PHP 数据库
PHP中哪个框架最适合做API?
在数字化时代,API作为软件应用间通信的桥梁至关重要。本文探讨了PHP中适合API开发的主流框架,包括Laravel、Symfony、Lumen、Slim、Yii和Phalcon,分析了它们的特点和优势,帮助开发者选择合适的框架,提高开发效率、保证接口稳定性和安全性。
29 3
|
14天前
|
SQL 安全 PHP
探索PHP的现代演进:从Web开发到框架创新
PHP是一种流行的服务器端脚本语言,自诞生以来在Web开发领域占据重要地位。从简单的网页脚本到支持面向对象编程的现代语言,PHP经历了多次重大更新。本文探讨PHP的现代演进历程,重点介绍其在Web开发中的应用及框架创新,如Laravel、Symfony等。这些框架不仅简化了开发流程,还提高了开发效率和安全性。
23 3
|
17天前
|
SQL 安全 PHP
探索PHP的现代演进:从Web开发到框架创新
PHP 自发布以来一直在 Web 开发领域占据重要地位,历经多次重大更新,从简单的脚本语言进化为支持面向对象编程的现代语言。本文探讨 PHP 的演进历程,重点介绍其在 Web 开发中的应用及框架创新。自 PHP 5.3 引入命名空间后,PHP 迈向了面向对象编程时代;PHP 7 通过优化内核大幅提升性能;PHP 8 更是带来了属性、刚性类型等新特性。
24 3
|
2月前
|
设计模式 SQL 安全
PHP中的设计模式:单例模式的深入探索与实践在PHP的编程实践中,设计模式是解决常见软件设计问题的最佳实践。单例模式作为设计模式中的一种,确保一个类只有一个实例,并提供全局访问点,广泛应用于配置管理、日志记录和测试框架等场景。本文将深入探讨单例模式的原理、实现方式及其在PHP中的应用,帮助开发者更好地理解和运用这一设计模式。
在PHP开发中,单例模式通过确保类仅有一个实例并提供一个全局访问点,有效管理和访问共享资源。本文详细介绍了单例模式的概念、PHP实现方式及应用场景,并通过具体代码示例展示如何在PHP中实现单例模式以及如何在实际项目中正确使用它来优化代码结构和性能。
46 2
|
2月前
|
关系型数据库 MySQL 数据库
docker启动mysql多实例连接报错Can’t connect to local MySQL server through socket ‘/var/run/mysqld/mysqld.sock’
docker启动mysql多实例连接报错Can’t connect to local MySQL server through socket ‘/var/run/mysqld/mysqld.sock’
184 0
|
3月前
|
安全 前端开发 PHP
构建与验证表单:传统PHP与Laravel框架的比较分析——探索Web开发中表单处理的优化策略和最佳实践
【8月更文挑战第31天】在 Web 开发中,表单构建与数据验证至关重要。传统 PHP 方法需手动处理 HTML 表单和数据验证,而 Laravel 框架则提供了一种更现代、高效的解决方案。本文通过对比传统 PHP 和 Laravel 的方法,探讨表单构建与验证的最佳实践。Laravel 通过简洁的语法糖、内置的数据过滤和验证机制,显著提升了代码的安全性和可维护性,适用于大型项目或需要快速开发的场景。然而,在追求灵活性的小型项目中,直接使用 PHP 仍是不错的选择。了解两者的优劣,有助于开发者根据项目需求做出最佳决策。
38 0
|
2月前
|
消息中间件 NoSQL Go
PHP转Go系列 | ThinkPHP与Gin框架之Redis延时消息队列技术实践
【9月更文挑战第7天】在从 PHP 的 ThinkPHP 框架迁移到 Go 的 Gin 框架时,涉及 Redis 延时消息队列的技术实践主要包括:理解延时消息队列概念,其能在特定时间处理消息,适用于定时任务等场景;在 ThinkPHP 中使用 Redis 实现延时队列;在 Gin 中结合 Go 的 Redis 客户端库实现类似功能;Go 具有更高性能和简洁性,适合处理大量消息。迁移过程中需考虑业务需求及系统稳定性。
|
3月前
|
缓存 中间件 PHP
Laravel 框架:优雅 PHP Web 开发的典范
【8月更文挑战第31天】
107 0
|
3月前
|
安全 Java 云计算
JSF 应用究竟何去何从?云端部署能否成为其全新突破点?快来一探究竟!
【8月更文挑战第31天】本文介绍了将JavaServer Faces(JSF)应用部署到云平台的过程。首先,根据成本、功能、可靠性和安全性选择合适的云平台。接着,展示了构建简单JSF应用的示例代码。最后,以AWS Elastic Beanstalk为例,详细说明了部署流程。部署至云端可提升应用的可用性、扩展性和安全性。
48 0