Menu

Reference / Http\WebSocket

Connection

finalclass
final class Connection implements \IteratorAggregate

One open WebSocket, server or client side.

Obtain one from connect() (client) or as the argument of the session callback given to upgrade() (server). Iterating it with foreach yields each incoming Message until the connection ends.

$ws = Http\WebSocket\connect('ws://127.0.0.1:8080/');
$ws->send('hello');
foreach ($ws as $m) {
    echo $m->data, "\n";
}

Reading. Only one task may read at a time; a second concurrent receive() throws LogicException. The connection is kept alive only while something reads it: the read timeout doubles as the keepalive ping timer (see Options::$pingInterval), so a connection nobody reads is never pinged. Code that only sends must still run a reader, even one that discards the messages.

Writing. send(), sendBinary() and ping() may be called from any task; concurrent writers are serialized, so frames never interleave on the wire. Every message goes out as a single frame.

Closing. close() starts the closing handshake. Called from the reading task it waits for the peer's answer (up to Options::$closeTimeout); called from any other task it sends the Close frame and returns immediately, and a deadline shuts the socket down if the peer does not answer in time.

Example

// An echo server and a client in one program, over loopback.

use function Async\async;
use function Async\spawn;
use Http\WebSocket as WS;

$l = stream_socket_server('tcp://127.0.0.1:0');
stream_set_blocking($l, false);
$name = stream_socket_get_name($l, false);
$port = (int)substr($name, strrpos($name, ':') + 1);
$server = \Http\Server::onListener($l)->acceptWait(0.02);

async(function () use ($server, $port) {
    spawn(function () use ($server) {
        $server->serve(function (\Http\Request $req): \Http\Response {
            return WS\upgrade($req, function (WS\Connection $ws): void {
                foreach ($ws as $m) {
                    if ($m->binary) {
                        $ws->sendBinary($m->data);
                    } else {
                        $ws->send('echo: ' . $m->data);
                    }
                }
            });
        });
    });

    $ws = WS\connect("ws://127.0.0.1:$port/");
    $ws->send('hello');
    $m = $ws->receive();
    echo $m->data, ' (', $m->binary ? 'binary' : 'text', ")\n";

    $ws->sendBinary("\x00\x01\x02");
    $m = $ws->receive();
    echo bin2hex($m->data), ' (', $m->binary ? 'binary' : 'text', ")\n";

    $ws->close(1000, 'done');
    echo 'open: ', var_export($ws->isOpen(), true), ', close code: ', $ws->closeCode(), "\n";
    $server->stop();
});

Output

echo: hello (text)
000102 (binary)
open: false, close code: 1000

See also: Options, Message, upgrade, connect

Methods

protocol()The subprotocol negotiated in the handshake.
request()The HTTP upgrade request that opened this connection.
remoteAddr()The address of the peer.
isOpen()Whether messages can still be sent.
closeCode()The peer's close code, valid once receive() has returned null.
closeReason()The reason text from the peer's Close frame.
getIterator()Iterate the incoming messages: foreach ($ws as $m) yields each Message until the connection ends.
receive()Wait for the next complete message.
send()Send a text message as one frame.
sendBinary()Send a binary message as one frame.
ping()Send a ping frame.
close()Start the closing handshake by sending a Close frame.

protocol()#source

function protocol(): string

The subprotocol negotiated in the handshake.

Returns

string — the protocol name, or '' when none was negotiated

request()#source

function request(): ?\Http\Request

The HTTP upgrade request that opened this connection.

Returns

\Http\Request|null — the request on the server side; null on a client connection

remoteAddr()#source

function remoteAddr(): string

The address of the peer.

Returns

string — the client's address on the server side; host:port of the URL on the client side

isOpen()#source

function isOpen(): bool

Whether messages can still be sent.

Returns

bool — false once the connection is closed or a Close frame has been sent

closeCode()#source

function closeCode(): int

The peer's close code, valid once receive() has returned null.

Returns

int — the code from the peer's Close frame; 1005 if that frame carried none; 1006 if no valid Close ever arrived (EOF, a protocol error, an unanswered ping). Never the code this side sent.

closeReason()#source

function closeReason(): string

The reason text from the peer's Close frame.

Returns

string — the reason, or '' when the peer sent none or no Close arrived

getIterator()#source

function getIterator(): \Generator

Iterate the incoming messages: foreach ($ws as $m) yields each Message until the connection ends. Equivalent to calling receive() until it returns null.

Returns

\Generator — yields Message objects

receive()#source

function receive(): ?Message

Wait for the next complete message.

Fragments are reassembled, compressed messages decompressed, and pings answered with a pong automatically. Returns null once the connection has ended; closeCode() and closeReason() say why. A protocol violation by the peer closes the connection with the matching code (1002 protocol error, 1007 invalid UTF-8 or corrupt compressed data, 1009 message or frame too big) and also yields null.

Returns

Message|null — the next message, or null when the connection is over

Throws

\LogicException
when another task is already reading this connection

send()#source

function send(string $data): void

Send a text message as one frame.

May be called from any task. The data must be valid UTF-8: a conforming peer closes the connection with 1007 on anything else.

Parameters

$data string
the message

Throws

ConnectionClosedException
when the connection is closed or closing

sendBinary()#source

function sendBinary(string $data): void

Send a binary message as one frame.

May be called from any task.

Parameters

$data string
the message bytes

Throws

ConnectionClosedException
when the connection is closed or closing

ping()#source

function ping(string $payload = ''): void

Send a ping frame. The peer's pong is consumed by the reader and not delivered as a message.

Parameters

$payload string = ''
up to 125 bytes echoed back in the pong

Throws

\ValueError
when the payload is longer than 125 bytes
ConnectionClosedException
when the connection is closed or closing

close()#source

function close(int $code = 1000, string $reason = ''): void

Start the closing handshake by sending a Close frame.

Called from the reading task (the task that last ran receive(), or the one that opened the connection before any receive()) it then waits for the peer's Close, up to Options::$closeTimeout, discarding any data frames that arrive meanwhile. Called from any other task, or while a receive() is in flight, it returns at once; a deadline of Options::$closeTimeout shuts the socket down if the peer does not answer, and the reader's receive() returns null with closeCode() 1006. Closing an already closing or closed connection does nothing.

Parameters

$code int = 1000
1000-1003, 1007-1014 or 3000-4999
$reason string = ''
UTF-8 text of at most 123 bytes

Throws

\ValueError
for a code that cannot be sent, a reason over 123 bytes, or a reason that is not valid UTF-8; nothing is sent

Example

// An echo server and a client in one program, over loopback.

use function Async\async;
use function Async\spawn;
use Http\WebSocket as WS;

$l = stream_socket_server('tcp://127.0.0.1:0');
stream_set_blocking($l, false);
$name = stream_socket_get_name($l, false);
$port = (int)substr($name, strrpos($name, ':') + 1);
$server = \Http\Server::onListener($l)->acceptWait(0.02);

async(function () use ($server, $port) {
    spawn(function () use ($server) {
        $server->serve(function (\Http\Request $req): \Http\Response {
            return WS\upgrade($req, function (WS\Connection $ws): void {
                foreach ($ws as $m) {
                    if ($m->binary) {
                        $ws->sendBinary($m->data);
                    } else {
                        $ws->send('echo: ' . $m->data);
                    }
                }
            });
        });
    });

    $ws = WS\connect("ws://127.0.0.1:$port/");
    $ws->send('hello');
    $m = $ws->receive();
    echo $m->data, ' (', $m->binary ? 'binary' : 'text', ")\n";

    $ws->sendBinary("\x00\x01\x02");
    $m = $ws->receive();
    echo bin2hex($m->data), ' (', $m->binary ? 'binary' : 'text', ")\n";

    $ws->close(1000, 'done');
    echo 'open: ', var_export($ws->isOpen(), true), ', close code: ', $ws->closeCode(), "\n";
    $server->stop();
});

Output

echo: hello (text)
000102 (binary)
open: false, close code: 1000

View source