Diskuze: PHP čtení websocket v příkazové řádce
V předchozím kvízu, Online test znalostí PHP, jsme si ověřili nabyté zkušenosti z kurzu.


No, a jak si to ale predstavujes? V web-socket jsem si hral zatim jen s chatem. Takze ti moc neporadim...
U chatu to funguje tak, ze si vytvoris v php nekonecnou smycku, ve ktere nacitas obsah socketu, pripojenych lidi. Aby ti smycka fungovala, musis vypnout casove omezeni pro spusteni kodu.
set_time_limit(0); // how long run server
- Prvne si tam dej 5-15 sekund, treba. za tu dobu stihnes spustit html stranku a testnout, zda se ti to pripoji.
Neprijemne totiz je, ze pokud to nechas v nekonecnu a nemas tam pridanou moznost zastaveni, tak ten proces bude mozne zhodit jen linuxovym prikazem pro odstraneni z procesu. Podobne na win.
- Dalsi zasadni problem je, ze seznam pripojenych uzivatelu-socketu je treba si ukladat do pole. To tak uplne v na php.net/socket_create jasne neni. Cili, ty si vytvoris nejake pole, kam ukladas vsechny conected na tvuj socket. A pak to pole prochazis a overujes, zda se na nekterem connect objevi zprava nebo ne (obvykle na masterovi). A kdyz jo, tak to posles vsem connected mimo mastera nebo ne.
A tez je teda dobre, pri kontrolovani mastera mit zabudovany prikaz na
zastaveni nekonecne smycky while.
Princip:
- Na zacatku si vytvoris mastera.
- U mastera kontrolujes, zda se k nemu nekdo pripoji, posle message na socket.
- Pokud jo, probiha to pomoci hand-shake. Uzivatel posle zadost o pripojeni, data o sobe. Tvuj program zadost prijme, vygeneruje certifikat (json kod), hand shake a pripoji ho do pole changed.
- A uzivatel si certifikat zapise a dale pouziva pri komunikaci s masterem
- Pokud uzivatel udela F5 v prohlizeci a nema platne pripojeni, pripojuje se znova. Takze ti takhle muze naskakar spousta otevrenych uzivatelu. A tez je to neprijemne pro uzivatele. Takze musis mit nejaky mechanismus k pripojovani a odpojovani uzivatelu na strane prohlizece i php-socket-serveru
- A take je dobre myslet na to, ze server si aktivitu na socketu monitoruje. Posila a prijima zpravy typu ping.
Ale, mozna, ze ty potrebujes neco jineho.
/*
public function writeError($str)//,$socket
{
$str .= " failed ";
// $str .= $socket && is_resource($socket) ? socket_strerror(socket_last_error($socket)) : '';
$str .= socket_strerror(socket_last_error());
echo $str.'<br>';
return false;
}
public function create($address,$port,$max_conn=20)
{
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or writeError("socket_create()",$socket);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1) or writeError("socket_set_option()",$socket);
socket_bind($socket, $address, $port) or writeError("socket_bind()",$socket);
socket_listen($socket,$max_conn) or writeError("socket_listen()",$socket);
//socket_set_nonblock($socket);
echo "Server Started : ".date('Y-m-d H:i:s')."\n";
echo "<br>Master socket : ".$socket."\n";
echo "<br>Listening on : address = ".$address.", port = ".$port."\n\n";
$this->master = $socket;
$this->sockets = array();
$this->users = array();
$this->userAdd($socket,array(
'name' => 'master'
)); // pridavam socked mastera do seznamu uzivatelu + do pole sockets
// a to pole pak presunuji do changed, $changed = $WS->sockets, ve while smycce
}
public function connect($cfg)
{
global $REQ;
// global $sockets,$users;
$socket = socket_accept($this->master) or $this->writeError("socket_accept()",$socket); //accept new socket
if ($socket)
{
$header = socket_read($socket, 1024) or $this->writeError("socket_read()",$socket); //read data sent by the socket
$this->createHandshake($socket, $header, $cfg); //perform websocket handshake
socket_getpeername($socket, $ip) or $this->writeError("socket_getpeername()",$socket);
$uniqid = $REQ->get('uniqid');
$name = $REQ->get('name');
return $this->userAdd($socket,array(
'name' => $name,
'uniqid' => $uniqid,
'ip' => $ip,
'handshake' => true
));
//$this->userAdd($socket,$ip,true);
// return $this->findUserBySocket($socket); // add user->index
}
return false;
//console($socket." CONNECTED!");
//return $user;
}
*/
$cfg = array(
'server' => array(
'host' => 'localhost', //host
'port' => '9000', //port
'max_conn' => 20,
'url' => ''
),
//'null' => NULL, //null var
'header_sep' => PHP_EOL ? PHP_EOL : "\r\n"
);
$cfg['server_url'] = "ws://".$cfg['server']['host'].":".$cfg['server']['port']."/demo/ws_server.php";//RTC-chat/php-ws
$cfg['server']['url'] = $cfg['server_url'];
$WS = new classWebSocket(); // Ja si na to udelal nejakou class a moznost zastavit beh. Ma to asi 20k. Ale podstatny je ten cyklus.
$WS->create($s['host'],$s['port'],$s['max_conn']);
$write_null = null;
$except_null = null;
$run = true;
$changed = $WS->sockets;
//var_dump($changed);
//die();
while($run)
{
$changed = $WS->sockets;
// socket_select($changed,$write=NULL,$except=NULL,NULL);
socket_select($changed,$write_null,$except_null,0,10);
foreach($changed as $socket)
{
if ($socket==$WS->master)
{
// $client=socket_accept($master);
$user = $WS->connect($cfg);
$ROOM->join($user);
// if($client<0)
// { console("socket_accept() failed"); continue; }
// else
// { connect($client); }
}
else {
$bytes = @socket_recv($socket,$buffer,2048,0);
if ($bytes==0)
{
// disconnect($socket);
$user = $WS->disconnect($socket);
$ROOM->leave($user);
}
else {
$user = $WS->findUserBy('socket',$socket);
if ($WS->receive($user, $buffer)==-999)
{$run = false;}
// if(!$user->handshake){ dohandshake($user,$buffer); }
// else{ process($user,$buffer); }
// $received_text = unmask($buf); //unmask data
// $msg = json_decode($received_text); //json decode
}
}
}
}
$WS->closeAll();
echo '<hr>'.$MICRO->stop();
Jestli to spravne chapu, tak ty tam smycku mas. Readujes master socketa.
Neresis nejake handshake. Neresis pripojeni uzivatele. Jen chces vedet, zda to
nejak zareaguje.
No, jenze, vypsani na obrazovku se obvykle deje az, kdyz php program ukonci svou
cinnost
A muzou nastat tyto situace:
- bud ti bezi cyklus stale dokola
- nebo se cyklus zastavi a vypisou se ti vsechna echa na obrazovku
- cyklus nebezi dostatecne dlouho, aby ses stihl javascriptem pripojit
Na read nemusi nutne nic byt. A tim padem ti to hned skonci.
Socketovat muzes tedy jen tak, ze nekde bezi server, ktery vysila data, kdyz se
na nej pripoji uzivatel. Neco, co bezi v nekonecne smycce porad dokola a ceka na
uzivatele. Read dela jen to, ze precte ze socketu. A protoze 99% casu na socketu
nic neni, tak tva smycka ihned skonci Coz se projevi tim, ze php ukonci zasilani stranky uzivateli a vypisou
se ti vsechna echa. (Dobre na tom je, ze program nezustane viset v pameti,
nemusis ho zhazovat z procesu
Spatne, ze se v tom kratkem okamziku nikdo nestihl pripojit, takze read nic
nevypise. )
Tvuj JS kod dela to, ze se pokusi pripojit na nejaky server a odeslat
zpravu.
- Takze si ten cyklus sprav na nekonecnou smycku
- Nebo, jestli chces mit neco zajimaveho, tak muzes cely js kod obalit do casovace a nechat ho probihat stale dokola, dokud nezmacknes klavesu, treba. Je tam sance, ze se trefis do okamziku, kdy ve vedlejsim okne spustis ten php program.
David Šabacký:15.6.2021 10:48
Ahoj
díky, ono je to i tom, že na tom websocketu se generují stále nějaké informace. Potřebuju jenom klienta, kterým to budu číst a parsovat.
D
A pise ti to teda nejakou chybu?
Protoze, jinak ten kod je asi dobre, az na to, ze php ma limit pro beh scriptu.
Pokud ten while teda bezi do nekonecka, tak nikdy neskonci. A tudiz nemuze
odeslat na obrazocku zadne echo. A cele to skonci chybou php-memory-limit nebo
php-time-limit.
Otazkou je, kam dal ty informace maji putovat?
Jakoze jsem to teda spatne pochopil Potrebujes funkcni js kod prepsat do php. Jenom cteni socketu.
Prvne bych si pred while napsal die(); a doplnil chybove hlasky ke kazdemu
prikazu, ktery bude reagovat na socket_strerror(socket_last_error()); Zjistit,
zda se to spravne pripoji, zda je spravna url adresa a tak.
Zkusim ten kod upravovat, jestli je to nejaky free server a pobezi mi to, treba
na neco prijdu
smazano...
Podarilo se mi to dostat do stavu, kdy mi server odpovida a vypisuje
content = HTTP/1.1 505 HTTP Version Not Supported Connection: close Server: Cowboy Date: Tue, 15 Jun 2021 12:56:52 GMT Content-Length: 0
// -------
<?php
error_reporting(E_ALL);
function writeError($source, $socket)
{
if ($socket)
{
$code = socket_last_error($socket);
socket_clear_error($socket);
}
else {
$code = socket_last_error();
}
$msg = socket_strerror($code);
echo "<hr>Error ($source) [".$code."] ".$msg.'<hr>';
return false;
}
echo "<h2>TCP/IP Connection</h2>\n";
// url = ws://demo.signalk.org/signalk/v1/stream?subscribe=none
$service = array();
$service['hostname'] = 'demo.signalk.org';
$service['service'] = 'www';
$service['protocol'] = 'tcp';
$service['port'] = getservbyname($service['service'], $service['protocol']);
$service['path'] = '/signalk/v1/stream?subscribe=none';
$service['path2'] = '/signalk/v1/stream';
$service['address'] = gethostbyname($service['hostname']);
/* Create a TCP/IP socket. */
echo "<br>"; echo "Attempting to connect to '{$service['address']}' on port '{$service['port']}', header-host '{$service['hostname']}{$service['path2']}' ...";
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or writeError("socket_create", $socket);
$bool = socket_connect($socket, $service['address'], $service['port']) or writeError("socket_create", $socket);
$opt = socket_get_option($socket);
echo "<br>"; var_dump('opt', $opt);
$status = socket_get_status($socket);
echo "<br>"; var_dump('status', $status);
echo "<br>"; echo "Sending HTTP HEAD request...";
$header[] = "HEAD / HTTP/1.1";
$header[] = "Host: ".$service['hostname'];
$header[] = "Connection: Close";
$header[] = "";
$header[] = "";
$header = implode(PHP_EOL, $header);
var_dump($header);
//$msg = '{"context": "vessels*","subscribe": [{"path": "navigation*", "period": 1000,"format": "delta", "policy": "instant", "minPeriod": 10000},{"path": "mmsi*","minPeriod": 1000,"policy": "ideal"}]}';
//socket_set_option($socket, SOL_SOCKET, SO_BROADCAST, 1) or $this->errorAdd("socket_set_option",$socket);
//socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1) or writeError("socket_set_option",$socket);
$result = socket_write($socket, $header, strlen($header));
echo "<br>"; var_dump('socket_write written bytes = ', $result);
$content = '';
echo "<br>"; echo "Reading response...";
$result = true;
while ($result==true) {
$result = socket_read($socket, 2048) or writeError("socket_read", $socket); //read data sent by the socket
echo "<br>"; var_dump('socket_read', $result);
$content .= $result;
}
echo "<br>"; echo 'content = '. $content;
echo "<br>"; echo "Closing socket...";
@socket_close($socket);
echo "<br>"; var_dump('socket_close');
echo time();
?>
/*
TCP/IP Connection
Attempting to connect to '52.212.52.84' on port '80', header-host 'demo.signalk.org/signalk/v1/stream' ...
string(3) "opt" NULL
string(6) "status" bool(false)
Sending HTTP HEAD request...string(58) "HEAD / HTTP/1.1 Host: demo.signalk.org Connection: Close "
string(29) "socket_write written bytes = " int(58)
Reading response...
string(11) "socket_read" string(134) "HTTP/1.1 505 HTTP Version Not Supported Connection: close Server: Cowboy Date: Tue, 15 Jun 2021 12:56:52 GMT Content-Length: 0 " Error (socket_read) [0] Success <<<<------------
string(11) "socket_read" string(0) ""
content = HTTP/1.1 505 HTTP Version Not Supported Connection: close Server: Cowboy Date: Tue, 15 Jun 2021 12:56:52 GMT Content-Length: 0
Closing socket...
string(12) "socket_close" 1623761812
*/
Zobrazeno 6 zpráv z 6.