v6.1.0-rc2
Pre-release
Pre-release
·
160 commits
to master
since this release
✨ New Features
- Added the
$throw_exceptionparameter toSwoole\Coroutine::cancel(), enabling exceptions to be thrown when canceling a coroutine. Swoole\Coroutine\Http\ClientandSwoole\Coroutine\Http\Servernow support receiving WebSocket fragmented messages.- Added
disconnectandpingmethods toSwoole\Coroutine\Http\ClientandSwoole\Coroutine\Http\Server. - Refactored
Swoole\Coroutine\Lock,Swoole\Lock, andSwoole\Thread\Lock, retaining only the__construct,lock, andunlockmethods, making usage more aligned withphp flock.
🐛 Bug Fixes
- Fixed an issue where
Swoole\Websocket\Servermight still trigger theonMessagecallback after a handshake failure or connection closure. - Fixed execution errors in
Swoole\Coroutine\Http\Servercaused by mismatched return types and function signatures. - Fixed the issue where
Swoole\Coroutine\Http\Serverdid not support enabling HTTP/2. - Fixed the timeout precision conversion issue in
Swoole\Lock::lockwait(). - Fixed the
ReactorEpoll::add(): failed to add eventwarning when using the coroutine-basedcurlmodule, caused by PHP 8 compatibility adjustments and incomplete cleanup ofkeepaliveconnections.
🛠️ Internal Optimizations
- Upgraded the
Swoole libraryversion. - Disabled the
-Wdate-timewarning to resolve compilation errors when using Zig/Clang. - Removed PHP 8.0 related compatibility code.
- Added the
SWOOLE_ODBC_LIBSvariable inconfig.m4. - Ensured that
timespec.tv_nsecvalues are less than1,000,000,000to 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\ClientandSwoole\Coroutine\Http\Serverwill automatically handle WebSocket control frames unlessopen_websocket_ping_frame,open_websocket_pong_frame, oropen_websocket_close_frameare explicitly set. Swoole\Coroutine::cancel()cannot cancel file coroutine operations. Forcibly canceling may cause segmentation faults.- If
--enable-iouringis enabled, a warning will be thrown if the kernel version orliburingversion does not meet the requirements. - The
Swoole\Websocket\Server::pushmethod will not automatically close the connection after sending a close frame. To close the connection, use theSwoole\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);
});