Workerman使用Swoole作为事件循环时, TcpConnection 发生异常。

wuheng

问题描述

程序使用的 workerman/gateway-worker。
因为业务端需要大量调用 第三方接口,所以需要使用异步http。
最开始用的 workerman/http-client。 但是第三方接口兼容有问题,每几百个请求就有一个超时的。
最终经过反复对比,打算用 Swoole 的携程调用 curl 请求第三方接口。

然后再 App.php 添加了 // 使用Swoole事件循环 利用携程发送异步请求
Worker::$eventLoopClass = \Workerman\Events\Swoole::class;
之后启动后,终端包参数不兼容。

报错原因是 Workerman使用Swoole作为事件循环时,发生类型不匹配,TcpConnection构造函数需要第三个参数$remoteAddress是字符串类型 但是传入了NULL值,所以PHP 8严格类型检查抛出了TypeError。

这个错误本身问题不大, 主要错误如下 就是参数问题。
手动修补 可以完美运行,但是这样只是临时解决方案。
请问有其他办法处理此错误吗?

TypeError: Workerman\Connection\TcpConnection::__construct(): Argument #3 ($remoteAddress) must be of type string, null given, called in /Users/wheng/phpDevelopment/UtalkLearn/vendor/workerman/workerman/src/Worker.php on line 2638 and defined in /Users/wheng/phpDevelopment/UtalkLearn/vendor/workerman/workerman/src/Connection/TcpConnection.php:369
{
    "name": "wheng/utalk-learn",
    "type": "project",
    "require": {
        "workerman/gateway-worker": "v4.0.0",
        "symfony/yaml": "^6.3",
        "php-amqplib/php-amqplib": "^3.6",
        "predis/predis": "^2.2",
        "overtrue/pinyin": "^5.3",
        "google/protobuf": "^4.27",
        "workerman/http-client": "^v3.0.3"
    },
    "autoload": {
        "classmap": [
            "src/model/"
        ],
        "psr-4": {
            "Proto\\Binarydata\\": "src/Proto/Binarydata/",
            "GPBMetadata\\Src\\Protobuf\\": "src/GPBMetadata/Src/Protobuf/"
        }
    },
    "repositories": [
        {
            "type": "composer",
            "url": "https://mirrors.cloud.tencent.com/composer/"
        }
    ],
    "config": {
        "audit": {
            "abandoned": "report"
        }
    }
}

程序代码


<?php

/**
 * run with command
 * php start.php start
 */
date_default_timezone_set('Asia/Shanghai');
ini_set('display_errors', 'on');

use Workerman\Worker;

if (strpos(strtolower(PHP_OS), 'win') === 0) {
    exit("start.php not support windows, please use start_for_win.bat\n");
}

// 检查扩展
if (!extension_loaded('pcntl')) {
    exit("Please install pcntl extension. See http://doc3.workerman.net/appendices/install-extension.html\n");
}

if (!extension_loaded('posix')) {
    exit("Please install posix extension. See http://doc3.workerman.net/appendices/install-extension.html\n");
}

// 标记是全局启动
define('GLOBAL_START', 1);

require_once __DIR__ . '/../vendor/autoload.php';

// 加载所有Applications/*/start.php,以便启动所有服务
foreach (glob(__DIR__ . '/start*.php') as $start_file) {
    require_once $start_file;
}

// 使用Swoole事件循环 利用携程发送异步请求
Worker::$eventLoopClass = \Workerman\Events\Swoole::class;

// 运行所有服务
Worker::runAll();

异步调用代码


<?php

declare(strict_types=1);

class AsyncExecutorClass
{
    /**
     * 执行异步任务
     * @param callable $task 任务逻辑
     * @param callable $callback 回调函数 function($result, $error)
     * @param mixed ...$args 任务参数
     */
    public static function run(callable $task, callable $callback, ...$args): void
    {
        \Workerman\Coroutine::create(function () use ($task, $callback, $args) {
            try {
                $result = $task(...$args);
                $callback($result, null);
            } catch (\Throwable $e) {
                $callback(null, $e);
            }
        });
    }
}

报错信息


(base) wheng@MacBook-Pro src % php App.php start
Workerman[App.php] start in DEBUG mode
-------------------------------------------------- WORKERMAN --------------------------------------------------
Workerman/5.1.1         PHP/8.1.32 (Jit off)          Darwin/22.6.0
--------------------------------------------------- WORKERS ---------------------------------------------------
event-loop  proto       user        worker            listen                      count       state            
swoole      tcp         wheng       BusinessWorker    none                        1            [OK]            
swoole      tcp         wheng       Gateway           websocket://0.0.0.0:8383    2            [OK]            
swoole      tcp         wheng       Register          text://0.0.0.0:2238         1            [OK]            
---------------------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.
TypeError: Workerman\Connection\TcpConnection::__construct(): Argument #3 ($remoteAddress) must be of type string, null given, called in /Users/wheng/phpDevelopment/UtalkLearn/vendor/workerman/workerman/src/Worker.php on line 2638 and defined in /Users/wheng/phpDevelopment/UtalkLearn/vendor/workerman/workerman/src/Connection/TcpConnection.php:369
Stack trace:
#0 /Users/wheng/phpDevelopment/UtalkLearn/vendor/workerman/workerman/src/Worker.php(2638): Workerman\Connection\TcpConnection->__construct(Object(Workerman\Events\Swoole), Resource id #50, NULL)
#1 /Users/wheng/phpDevelopment/UtalkLearn/vendor/workerman/workerman/src/Events/Swoole.php(288): Workerman\Worker->acceptTcpConnection(Resource id #45)
#2 [internal function]: Workerman\Events\Swoole->Workerman\Events\{closure}()
#3 {main}

截图报错信息里报错文件相关代码

截图

操作系统及workerman/webman等框架组件具体版本

截图

244 3 0
3个回答

wuheng

截图

if ($remoteAddress === null) {
            $remoteAddress = stream_socket_get_name($newSocket, true);
        }

可以使用 stream_socket_get_name 二次 获得这个 链接名字。。

  • 暂无评论
wuheng

能看得出来, 是 start_gateway.php 没有为 Swoole 提供兼容支持!!

我最初是头痛医头脚痛医脚, 在 TcpConnection 添加如下代码

if ($remoteAddress === null) {
    $remoteAddress = stream_socket_get_name($newSocket, true);
}

添加兼容代码后,业务能跑起来, 但是偶尔还是报错!主要是 gateway 通信不畅。

看了 github 上一位仁兄的解决方案后,得到灵感。
在 start_gateway.php 添加代码如下


$gateway->onConnect = function ($connection) {
    // echo "网关发生链接 client connected\n";
    $connection->onWebSocketConnect = function ($connection, $http_header) {};
};

$gateway->eventLoop = \Workerman\Events\Select::class;

// 如果不是在根目录启动,则运行runAll方法
if (!defined('GLOBAL_START')) {
    Worker::runAll();
}

项目正常运转了。。。

(base) wheng@MacBook-Pro src % php App.php start
Workerman[App.php] start in DEBUG mode
-------------------------------------------------- WORKERMAN --------------------------------------------------
Workerman/5.1.1         PHP/8.1.32 (Jit off)          Darwin/22.6.0
--------------------------------------------------- WORKERS ---------------------------------------------------
event-loop  proto       user        worker            listen                      count       state            
swoole      tcp         wheng       BusinessWorker    none                        1            [OK]            
select      tcp         wheng       Gateway           websocket://0.0.0.0:8383    1            [OK]            
swoole      tcp         wheng       Register          text://0.0.0.0:2238         1            [OK]            
---------------------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.

Image

  • 暂无评论

gatewayWorker不支持协程,目前没有计划让gatewayWorker支持协程,主要是没有精力和动力。
原因:

  1. gatewayWorker 里使用了全局变量 $_SESSION $_SERVER,这些在协程中会造成变量污染问题,需要花很多精力重构。
  2. 因为swoole和swow没有很好的兼容php默认行为,导致会出现一些不符合预期的情况,找出并兼容这些并保证可靠性需要花费时间。
  3. 手上有多个开源项目,维护和答疑花费了我大量时间。目前主要精力放到workerman和webman,没有办法时间和动力把其它开源项目做到完美。

不过可以把业务都转移到webman来做,在webman里使用协程,gatewayWorker只做通讯。

  • 暂无评论
×
🔝