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

2021-12-08 13:51:21 浏览数 (1)

安装

首先通过 composer 安装

代码语言:javascript复制
composer require topthink/think-worker
复制代码

使用

SocketServer

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

代码语言:javascript复制
php think worker:server
复制代码

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

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

配置参数

描述

protocol

协议

host

监听地址

port

监听端口

socket

完整的socket地址

并且支持workerman所有的参数(包括全局静态参数)。 也支持使用闭包方式定义相关事件回调。

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

复制

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

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

代码语言:javascript复制
return [
	'worker_class'	=>	'apphttpWorkerman',
];
复制代码

apphttpWorkerman.php

代码语言:javascript复制
<?php

namespace apphttp;

use thinkLog;
use thinkworkerServer;
use WorkermanLibTimer;

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 $msgn";
    }

    /**
     * 发送给所有人
     */
    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()等方法的正确性.

代码语言:javascript复制
// $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:先新建一个指令,参考文档:自定义指令,比如新建命令:

代码语言:javascript复制
php think make:command Hello hello
复制代码

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

代码语言:javascript复制
  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:运行的时候,使用如下命令:

代码语言:javascript复制
//临时运行
php think hello start
//后台运行
php think hello start --mode d
复制代码

0 人点赞