Native WebSocket extension for PHP.
ext-websocket keeps RFC 6455 protocol work in C and exposes a small PHP API for synchronous PHP code, async runtimes, and the native server runtime included in the extension.
- PHP >= 8.1
- Linux, macOS, BSD, or another POSIX-compatible OS
phpize,php-config, make, and a C compiler
pie install axcherednikov/php-websocketphpize
./configure --enable-websocket
make
make test
sudo make installEnable the extension:
extension=websocketCheck that PHP can load it:
php -m | grep websocketWith Homebrew PHP:
/opt/homebrew/opt/php@8.3/bin/phpize
./configure --enable-websocket --with-php-config=/opt/homebrew/opt/php@8.3/bin/php-config
make -j"$(sysctl -n hw.ncpu)"<?php
use WebSocket\Connection;
use WebSocket\MessageType;
use WebSocket\Server;
$server = new Server();
$server->listen('127.0.0.1', 8080);
$server->subprotocols('chat.v1');
$server->onOpen(static function (Connection $connection): void {
echo "open {$connection->id}";
echo $connection->subprotocol ? " ({$connection->subprotocol})\n" : "\n";
});
$server->onMessage(static function (Connection $connection, string $message): void {
$connection->send($message, MessageType::Text);
});
$server->onClose(static function (Connection $connection): void {
echo "close {$connection->id}\n";
});
$server->run();See examples/run_server.php for a runnable example.
<?php
use WebSocket\MessageType;
use WebSocket\Protocol;
$bytes = Protocol::encode('hello', MessageType::Text);
$frame = Protocol::decode($bytes);
echo $frame->payload; // helloOptions:
| Option | Description |
|---|---|
maxMessageSize |
Maximum incoming text/binary message size; defaults to 16 MiB |
maxQueuedBytes |
Maximum outgoing queued bytes per connection; defaults to 16 MiB |
maxConnections |
Maximum concurrently accepted TCP connections; defaults to 10000 |
handshakeTimeoutMs |
Maximum idle time before HTTP Upgrade completes; defaults to 10000 ms |
idleTimeoutMs |
Maximum idle time after HTTP Upgrade completes; defaults to 120000 ms |
pingIntervalMs |
Idle time before sending an automatic ping; 0 disables heartbeat pings by default |
pongTimeoutMs |
Maximum time to wait for a pong after an automatic ping; defaults to 10000 ms |
Prefer WebSocket\ServerOptions for explicit configuration. Associative arrays remain supported for compatibility.
Methods:
| Method | Description |
|---|---|
__construct(ServerOptions|array $options = []) |
Create a server |
listen(string $host, int $port): void |
Bind address for run() |
subprotocols(string ...$protocols): void |
Configure supported Sec-WebSocket-Protocol tokens |
onHandshake(Closure $handler): void |
Accept or reject valid HTTP Upgrade requests before 101 Switching Protocols |
onOpen(Closure $handler): void |
Register upgraded connection callback |
onMessage(Closure $handler): void |
Register text/binary message callback |
onClose(Closure $handler): void |
Register close callback |
onError(Closure $handler): void |
Register runtime error callback |
run(): void |
Start accept, HTTP Upgrade, and frame processing loop |
stop(): void |
Request shutdown |
getDriver(): string |
Return selected I/O driver |
Handshake callbacks receive a WebSocket\Request. Return normally to continue the WebSocket upgrade, or throw WebSocket\HandshakeException to reject it before 101 Switching Protocols is sent:
$server->onHandshake(static function (WebSocket\Request $request): void {
if ($request->header('Origin') !== 'https://app.test') {
throw new WebSocket\HandshakeException(
new WebSocket\HandshakeResponse(403, ['X-Reject' => 'origin'])
);
}
});| Method / property | Description |
|---|---|
header(string $name): ?string |
Return a case-insensitive request header value |
readonly string $method |
HTTP request method |
readonly string $target |
HTTP request target |
readonly array $headers |
Lower-case request headers |
| Method / property | Description |
|---|---|
__construct(int $status = 403, array $headers = [], string $body = '') |
Create a custom handshake rejection response |
readonly int $status |
HTTP status code |
readonly array $headers |
HTTP response headers |
readonly string $body |
HTTP response body |
| Method / property | Description |
|---|---|
__construct(?HandshakeResponse $response = null) |
Create a handshake rejection exception; defaults to 403 Forbidden |
readonly HandshakeResponse $response |
HTTP response sent before closing the connection |
| Method / property | Description |
|---|---|
send(string $payload, MessageType $type = MessageType::Text): void |
Send text, binary, or control payload |
close(int $code = 1000, string $reason = ''): void |
Send close frame and close the connection |
isOpen(): bool |
Check connection state |
readonly string $id |
Connection id |
readonly string $remoteAddress |
Remote peer address |
readonly ?string $subprotocol |
Negotiated subprotocol, or null |
| Method | Description |
|---|---|
acceptKey(string $key): string |
Build Sec-WebSocket-Accept |
encode(string $payload, MessageType $type = MessageType::Text, bool $masked = false): string |
Encode one frame |
decode(string $buffer): Frame|CloseFrame|null |
Decode one frame |
pack(string|Frame|CloseFrame $data, int $opcode = Protocol::OPCODE_TEXT, int $flags = Protocol::FLAG_FIN): string |
Encode with raw opcode and flags |
unpack(string $buffer): Frame|CloseFrame|null |
Decode with raw opcode and flags |
| Class | Description |
|---|---|
Frame |
Non-close WebSocket frame with type, opcode, flags, payload, final, and bytesConsumed |
CloseFrame |
Close frame with code, reason, flags, and bytesConsumed |
MessageType |
Enum cases: Continuation, Text, Binary, Ping, Pong, Close |
Detailed benchmark results and commands live in the separate php-websocket-bench repository.
The benchmark suite covers protocol encode/decode, server upgrade runtime, and real ws:// / wss:// message runtime against AMPHP, Workerman, OpenSwoole, and Ratchet.
See docs/production.md for deployment notes and current runtime limits.
Use websocket.stub.php as an IDE/static-analysis stub for PhpStorm, PHPStan, Psalm, or similar tools.
make testTo run tests against the built module directly:
TEST_PHP_ARGS="-d extension=$PWD/modules/websocket.so" php run-tests.php -q testsSee CHANGELOG.md.
MIT. See LICENSE.