Skip to content

Swoole HTTPS keep-alive 响应内容较大时,会出现最后一段数据需要在链接断开时才会触发事件回调的情况 #39

@Lin07ux

Description

@Lin07ux

代码如下:

$connection = $request->connection;

$client = new Client([
      'max_conn_per_addr' => 128, // 每个域名最多维持多少并发连接
      'keepalive_timeout' => 30,  // 连接多长时间不通讯就关闭
      'connect_timeout'   => 5,   // 连接超时时间
      'timeout'           => 120,  // 请求发出后等待响应的超时时间
  ]);

$client->request('https://app.unpkg.com/vue@3.5.17/files/dist/vue.esm-browser.js', [
    'method' => 'GET',
    'version' => '1.1',
    'headers' => ['Connection' => 'keep-alive'],
    'progress' => function($buffer) use ($connection) {
          $connection->send(new Chunk(time()." 响应大小: " . strlen($buffer).PHP_EOL));
    },
    'success' => function($response) use ($connection) {
          $connection->send(new Chunk(time()." 响应成功"));
          $connection->send(new Chunk('')); // 发送空的的chunk代表response结束
    },
    'error' => function (\Throwable $exception) use ($connection) {
        $connection->send(new Chunk(time()." 响应失败: ".$exception->getMessage()));
        $connection->send(new Chunk(''));
    },
]);

return new Response(200, ["Transfer-Encoding" => "chunked"], '');

请求的这个 js 文件大小为 535 kB,由于开启了 keepalive,所以服务器会保持链接 60s,此时会出现能正常速度的输出很大一部分的响应大小,然后在最后一点内容的时候会一直 hang 住,直到链接被服务器关闭,才能正常的输出响应成功,或者是读取超时被 ConnectionPool 主动断开,如下所示:

....
1750845988 响应大小: 8192
1750845988 响应大小: 8192
1750845988 响应大小: 8192
1750846107 响应失败: read 104.18.0.22:443 timeout after 120 seconds

但如果将headers中的Connection设置为close,即:

'headers' => ['Connection' => 'close'],

此时再请求就会很正常的完成输出:

...
1750845798 响应大小: 8192
1750845798 响应大小: 8192
1750845798 响应大小: 7295
1750845798 响应成功

环境:

> php -v
PHP 8.2.8 (cli) (built: Jul 10 2023 23:07:42) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.8, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.8, Copyright (c), by Zend Technologies

> php --ri swoole

swoole

Swoole => enabled
Author => Swoole Team <team@swoole.com>
Version => 6.0.2
Built => Jun 25 2025 10:47:55
coroutine => enabled with boost asm context
epoll => enabled
eventfd => enabled
signalfd => enabled
cpu_affinity => enabled
spinlock => enabled
rwlock => enabled
sockets => enabled
openssl => OpenSSL 3.1.8 11 Feb 2025
dtls => enabled
http2 => enabled
json => enabled
curl-native => enabled
curl-version => 8.12.1
c-ares => 1.19.1
zlib => 1.2.13
brotli => E16777225/D16777225
zstd => 1.5.5
mutex_timedlock => enabled
pthread_barrier => enabled
futex => enabled
mysqlnd => enabled
io_uring => enabled

Directive => Local Value => Master Value
swoole.enable_library => On => On
swoole.enable_fiber_mock => Off => Off
swoole.enable_preemptive_scheduler => Off => Off
swoole.display_errors => On => On
swoole.use_shortname => On => On
swoole.unixsock_buffer_size => 8388608 => 8388608

根据代码追踪,可以确认是\Workerman\Events\Swoole::callRead()方法没有被触发,表现就是最后一段响应长度较小,未能出发 Swoole Event 的可读事件,导致这一部分数据一直在 buffer 中,无法被 PHP 代码读取。具体原因受限于我的知识面,无法深入了解了。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions