Menu
Reference
- Manticore\Ds
- Async
- Http
- Http\WebSocket
- Interfaces
- Handler
- Classes
- Connection
- ConnectionClosedException
- HandshakeException
- Hub
- Message
- Options
- Functions
- connect()
- handler()
- run()
- upgrade()
- Buffer
- Process
- Io\Poll
- Manticore\Sapi
- Ffi
Connection
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: 1000See 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
$datastring- 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
$datastring- 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
$payloadstring = ''- 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
$codeint = 1000- 1000-1003, 1007-1014 or 3000-4999
$reasonstring = ''- 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