Turned around, the same loop is a server. The listening socket becomes readable when a client is waiting, and stream_socket_accept() returns that client's stream:
<?php
$server = stream_socket_server('tcp://127.0.0.1:8223');
$clients = [];
while (true) {
$read = $clients + ['listen' => $server];
stream_select($read, $write, $except, null); // sleep until something happens
foreach ($read as $id => $sock) {
if ($id === 'listen') { // a new client is waiting
$c = stream_socket_accept($server);
$clients[(int) $c] = $c;
} elseif (($line = fgets($sock)) === false) { // the peer hung up
echo "client $id left\n";
fclose($sock); // close now, or the peer waits for our FIN
unset($clients[$id]);
} else {
echo "client $id said: $line";
fwrite($sock, strtoupper($line));
}
}
}Two nc clients connected at once, the second sending between the first one's two lines. Each got its own lines back in uppercase, and the server logged:
client 6 said: first line client 7 said: second client client 7 left client 6 said: bye client 6 left
ext/sockets mirrors the C API for UDP and raw options; stream sockets are simpler and speak TLS. Beware fgets() on a blocking socket: half a line from one client stalls all the others.