axcherednikov / php-websocket
Native PHP extension providing RFC 6455 WebSocket protocol helpers and a WebSocket server runtime.
Package info
github.com/axcherednikov/php-websocket
Language:C
Type:php-ext
Ext name:ext-websocket
pkg:composer/axcherednikov/php-websocket
Requires
- php: >=8.1
This package is auto-updated.
Last update: 2026-05-25 17:35:23 UTC
README
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.
Requirements
- PHP >= 8.1
- Linux, macOS, BSD, or another POSIX-compatible OS
phpize,php-config, make, and a C compiler
Installation
Via PIE
pie install axcherednikov/php-websocket
From Source
phpize
./configure --enable-websocket
make
make test
sudo make install
Enable the extension:
extension=websocket
Check that PHP can load it:
php -m | grep websocket
With 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)"
Quick Start
<?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.
Protocol Helpers
<?php use WebSocket\MessageType; use WebSocket\Protocol; $bytes = Protocol::encode('hello', MessageType::Text); $frame = Protocol::decode($bytes); echo $frame->payload; // hello
Public API
WebSocket\Server
Options:
| 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']) ); } });
WebSocket\Request
| 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 |
WebSocket\HandshakeResponse
| 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 |
WebSocket\HandshakeException
| 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 |
WebSocket\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 |
WebSocket\Protocol
| 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 |
Value Objects
| 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 |
Benchmarks
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.
Production
See docs/production.md for deployment notes and current runtime limits.
IDE Support
Use websocket.stub.php as an IDE/static-analysis stub for PhpStorm, PHPStan, Psalm, or similar tools.
Testing
make test
To run tests against the built module directly:
TEST_PHP_ARGS="-d extension=$PWD/modules/websocket.so" php run-tests.php -q tests
Changelog
See CHANGELOG.md.
License
MIT. See LICENSE.