✨ New Features
- Added the
$throw_exception
parameter to Swoole\Coroutine::cancel()
, enabling exceptions to be thrown when canceling a coroutine.
Swoole\Coroutine\Http\Client
and Swoole\Coroutine\Http\Server
now support receiving WebSocket fragmented messages.
- Added
disconnect
and ping
methods to Swoole\Coroutine\Http\Client
and Swoole\Coroutine\Http\Server
.
- Refactored
Swoole\Coroutine\Lock
, Swoole\Lock
, and Swoole\Thread\Lock
, retaining only the __construct
, lock
, and unlock
methods, making usage more aligned with php flock
.
🐛 Bug Fixes
- Fixed an issue where
Swoole\Websocket\Server
might still trigger the onMessage
callback after a handshake failure or connection closure.
- Fixed execution errors in
Swoole\Coroutine\Http\Server
caused by mismatched return types and function signatures.
- Fixed the issue where
Swoole\Coroutine\Http\Server
did not support enabling HTTP/2.
- Fixed the timeout precision conversion issue in
Swoole\Lock::lockwait()
.
- Fixed the
ReactorEpoll::add(): failed to add event
warning when using the coroutine-based curl
module, caused by PHP 8 compatibility adjustments and incomplete cleanup of keepalive
connections.
🛠️ Internal Optimizations
- Upgraded the
Swoole library
version.
- Disabled the
-Wdate-time
warning to resolve compilation errors when using Zig/Clang.
- Removed PHP 8.0 related compatibility code.
- Added the
SWOOLE_ODBC_LIBS
variable in config.m4
.
- Ensured that
timespec.tv_nsec
values are less than 1,000,000,000
to comply with POSIX standards.
⚠️ Notes
- This version is an RC (Release Candidate) and should only be used in testing environments. Do not deploy it in production.
- Starting from Swoole 6.1,
Swoole\Coroutine\Http\Client
and Swoole\Coroutine\Http\Server
will automatically handle WebSocket control frames unless open_websocket_ping_frame
, open_websocket_pong_frame
, or open_websocket_close_frame
are explicitly set.
Swoole\Coroutine::cancel()
cannot cancel file coroutine operations. Forcibly canceling may cause segmentation faults.
- If
--enable-iouring
is enabled, a warning will be thrown if the kernel version or liburing
version does not meet the requirements.
- The
Swoole\Websocket\Server::push
method will not automatically close the connection after sending a close frame. To close the connection, use the Swoole\Websocket\Server::disconnect()
method.
- When processing control frames, the Swoole protocol actively sets the FIN flag to 1 and ensures compression is not enabled (i.e., the RSV1 bit is 0). User settings for this will be ignored.
- When handling compression and sending of consecutive frames, Swoole delegates the responsibility of compression and data chunking to the application layer. Users must manually implement the relevant logic. Refer to the example below for details.
💡 API Changes
New Lock API
Function Prototypes
namespace Swoole\Coroutine {
class Lock {
public function __construct(bool $shared = false) {}
public function lock(int $operation = LOCK_EX): bool {}
public function unlock(): bool {}
}
}
namespace Swoole {
class Lock {
public function __construct(int $type = SWOOLE_MUTEX) {}
public function lock(int $operation = LOCK_EX, float $timeout = -1): bool {}
public function unlock(): bool {}
}
}
namespace Swoole\Thread {
class Lock {
public function __construct(int $type = SWOOLE_MUTEX) {}
public function lock(int $operation = LOCK_EX, float $timeout = -1): bool {}
public function unlock(): bool {}
}
}
Usage Example
$lock = new Swoole\Lock;
$lock->lock(LOCK_EX | LOCK_NB, 0.5);
$lock->unlock();
Canceling Coroutines
Co\run(function () {
$cid = Co\go(function () {
try {
while (true) {
System::sleep(0.1);
echo "co 2 running\n";
}
var_dump('end');
} catch (Swoole\Coroutine\CanceledException $e) {
var_dump('cancelled');
}
});
System::sleep(0.3);
Co::cancel($cid, true);
System::sleep(0.2);
echo "co 1 end\n";
});
WebSocket Compression and Sending Consecutive Frames
Asynchronous Server
use Swoole\WebSocket\Server;
use Swoole\WebSocket\Frame;
$server = Server('127.0.0.1', 9502);
$server->set(['websocket_compression' => true]);
$server->on('message', function (Server $server, Frame $frame) {
$data1 = bin2hex(random_bytes(10 * 1024));
$data2 = bin2hex(random_bytes(20 * 2048));
$data3 = bin2hex(random_bytes(40 * 4096));
$context = deflate_init(ZLIB_ENCODING_RAW); // Stream compression
$server->push($frame->fd, deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
$server->push($frame->fd, deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
$server->push($frame->fd, deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
});
$server->start();
Coroutine Server
use function Swoole\Coroutine\run;
use Swoole\Coroutine\Http\Server;
run(function() {
$server = new Server("127.0.0.1", 9502);
$server->set(['websocket_compression' => true]);
$server->handle('/', function ($request, $response) use ($data1, $data2, $data3) {
$response->upgrade();
$data1 = bin2hex(random_bytes(10 * 1024));
$data2 = bin2hex(random_bytes(20 * 2048));
$data3 = bin2hex(random_bytes(40 * 4096));
$context = deflate_init(ZLIB_ENCODING_RAW);
$response->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
$response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
$response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
});
});
Coroutine Client
use function Swoole\Coroutine\run;
use Swoole\Coroutine\Http\Client;
run(function() {
$client = new Client('127.0.0.1', 9502);
$client->set(['websocket_compression' => true]);
$ret = $client->upgrade('/');
$context = deflate_init(ZLIB_ENCODING_RAW);
$client->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
$client->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
$client->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
});
✨ 新功能
Swoole\Coroutine::cancel()
新增 $throw_exception
参数,支持在取消协程时抛出异常。
Swoole\Coroutine\Http\Client
与 Swoole\Coroutine\Http\Server
现已支持接收 WebSocket 分块消息。
- 为
Swoole\Coroutine\Http\Client
和 Swoole\Coroutine\Http\Server
新增 disconnect
与 ping
方法。
- 重构
Swoole\Coroutine\Lock
,Swoole\Lock
和,Swoole\Thread\Lock
,只保留__construct
,lock
和unlock
方法,使用上更加贴近php flock
。
🐛 问题修复
- 修复
Swoole\Websocket\Server
在握手失败或连接关闭后,仍可能触发 onMessage
回调的问题。
- 修复
Swoole\Coroutine\Http\Server
因函数返回值类型与签名不一致导致的执行错误。
- 修复
Swoole\Coroutine\Http\Server
不支持启用 HTTP/2 的问题。
- 修复
Swoole\Lock::lockwait()
超时时间精度转换问题。
- 修复因
PHP 8
兼容性调整及keepalive
连接清理不彻底,导致使用协程化 curl
模块时出现 ReactorEpoll::add(): failed to add event
警告的问题。
🛠️ 内部优化
- 升级
Swoole library
版本。
- 禁用
-Wdate-time
警告,解决使用 Zig/Clang 编译时的错误。
- 移除 PHP 8.0 相关兼容代码。
- 在
config.m4
中新增 SWOOLE_ODBC_LIBS
变量。
- 确保
timespec.tv_nsec
取值小于 1,000,000,000
,以符合 POSIX 标准规范。
⚠️ 注意事项
- 本版本为 RC(候选发布)版本,请仅在测试环境中使用,请勿在生产环境部署。
- 自 Swoole 6.1 起,
Swoole\Coroutine\Http\Client
与 Swoole\Coroutine\Http\Server
将自动处理 WebSocket 控制帧,除非显式设置 open_websocket_ping_frame
、open_websocket_pong_frame
或 open_websocket_close_frame
。
Swoole\Coroutine::cancel()
无法取消文件协程化操作,强行取消可能导致段错误。
- 若启用
--enable-iouring
,在检测到内核版本或 liburing
版本不满足要求时,将抛出警告信息。
Swoole\Websocket\Server::push
方法在发送关闭帧后不会自动关闭连接,如需关闭连接,请使用 Swoole\Websocket\Server::disconnect()
方法。
Swoole
协议在处理控制帧时,会主动设置 FIN 标志位为 1,并确保不启用压缩(即 RSV1 位为 0),用户对此的设置将被忽略。
- 当处理连续帧的压缩和发送时,
Swoole
将压缩与数据分块的职责移交至应用层,用户需手动实现相关逻辑。具体请看最下面的例子。
💡 API 变更
新的锁 API
函数原型
namespace Swoole\Coroutine {
class Lock {
public function __construct(bool $shared = false) {}
public function lock(int $operation = LOCK_EX): bool {}
public function unlock(): bool {}
}
}
namespace Swoole {
class Lock {
public function __construct(int $type = SWOOLE_MUTEX) {}
public function lock(int $operation = LOCK_EX, float $timeout = -1): bool {}
public function unlock(): bool {}
}
}
namespace Swoole\Thread {
class Lock {
public function __construct(int $type = SWOOLE_MUTEX) {}
public function lock(int $operation = LOCK_EX, float $timeout = -1): bool {}
public function unlock(): bool {}
}
}
使用示例
$lock = new Swoole\Lock;
$lock->lock(LOCK_EX | LOCK_NB, 0.5);
$lock->unlock();
取消协程
Co\run(function () {
$cid = Co\go(function () {
try {
while (true) {
System::sleep(0.1);
echo "co 2 running\n";
}
var_dump('end');
} catch (Swoole\Coroutine\CanceledException $e) {
var_dump('cancelled');
}
});
System::sleep(0.3);
Co::cancel($cid, true);
System::sleep(0.2);
echo "co 1 end\n";
});
WebSocket 压缩和发送连续帧
异步服务端
use Swoole\WebSocket\Server;
use Swoole\WebSocket\Frame;
$server = Server('127.0.0.1', 9502);
$server->set(['websocket_compression' => true]);
$server->on('message', function (Server $server, Frame $frame) {
$data1 = bin2hex(random_bytes(10 * 1024));
$data2 = bin2hex(random_bytes(20 * 2048));
$data3 = bin2hex(random_bytes(40 * 4096));
$context = deflate_init(ZLIB_ENCODING_RAW); // 流式压缩
$server->push($frame->fd, deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
$server->push($frame->fd, deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
$server->push($frame->fd, deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
});
$server->start();
协程服务端
use function Swoole\Coroutine\run;
use Swoole\Coroutine\Http\Server;
run(function() {
$server = new Server("127.0.0.1", 9502);
$server->set(['websocket_compression' => true]);
$server->handle('/', function ($request, $response) use ($data1, $data2, $data3) {
$response->upgrade();
$data1 = bin2hex(random_bytes(10 * 1024));
$data2 = bin2hex(random_bytes(20 * 2048));
$data3 = bin2hex(random_bytes(40 * 4096));
$context = deflate_init(ZLIB_ENCODING_RAW);
$response->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
$response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
$response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
});
});
协程客户端
use function Swoole\Coroutine\run;
use Swoole\Coroutine\Http\Client;
run(function() {
$client = new Client('127.0.0.1', 9502);
$client->set(['websocket_compression' => true]);
$ret = $client->upgrade('/');
$context = deflate_init(ZLIB_ENCODING_RAW);
$client->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
$client->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
$client->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
});