PHP & Websockets

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Sjef dresen

sjef dresen

03/11/2021 20:05:11
Quote Anchor link
Beste allemaal,

Graag zou ik wat data versturen vanuit een telefoon naar een iPad aan de muur. Als "tussenpersoon" had ik mijn NAS in gedachte met daarop een PHP script met Websockets.

Het belangrijkste gedeelte (waarvan ik zelf dacht dat dit het moeilijkste was) heb ik al werkend: de Websocket op de NAS. Ik heb daarvoor dit voorbeeld gebruikt: https://medium.com/@cn007b/super-simple-php-websocket-example-ea2cd5893575

Op de NAS staan nu beide bestanden:

- websockets.php
- websockets.html

Als ik nu op de iPad naar http:\\IP-van-NAS\websockets.html ga, dan zie ik het resultaat van het test-script. (namelijk een teller die iedere seconde update).

Nu moet ik nog iets verzinnen om de data vanuit de telefoon (via het Websocket) naar de html van de iPad te krijgen.

Ik heb een PHP-script in gedachte dat ik ook op de NAS laat draaien, en dat als URL-parameters (via GET) de data vanuit de telefoon "pakt" en op de een of andere manier "doorgeeft" aan het Websocket. Maar hoe?

Hoe krijg ik (in een PHP script) data -die via $data = $_GET['data']; in de variabele $data zit- doorgestuurd naar dat Websocket dat (via websockets.php) op de NAS draait? (om het vervolgens dus in websockets.html zichtbaar te krijgen, dit laatste werkt dus al!)

Alvast heel erg bedankt voor het meedenken.

Groet,

Sjef
 
PHP hulp

PHP hulp

01/10/2022 19:51:42
 
- Ariën  -
Beheerder

- Ariën -

03/11/2021 20:35:53
Quote Anchor link
Je kan toch in je $content gewoon $_GET['data'] gebruiken?
 
Rob Doemaarwat

Rob Doemaarwat

03/11/2021 21:17:52
Quote Anchor link
De lompe manier:

- Je websockets.php draait in een loop. Kijk in die loop of er op een bepaalde plek een bepaald bestandje (met de "data") staat (daar neer gezet door het "data script"), en speel dat dan door via de WebSocket naar de iPad (en verwijder daarna het bestand). Gebruik hiervoor een RAM-disk, anders sta je steeds je disks te poetsen.

De nette manier:

- Vanuit het PHP script dat de data opvangt open je ook een connectie naar je websockets.php en geef je zo de informatie door. Dit kan vrij eenvoudig met een sockets (zie https://www.php.net/manual/en/ref.sockets.php ). Let op: dit is wat anders als de WebSocket! Kies een vrije poort, en laat websockets.php daar op luisteren. Vervolgens stuur je vanuit je data script de data door.
 
Sjef dresen

sjef dresen

04/11/2021 07:14:41
Quote Anchor link
- Ariën - op 03/11/2021 20:35:53:
Je kan toch in je $content gewoon $_GET['data'] gebruiken?


Ik denk dat je mijn vraag niet goed snapt, want ik zou niet weten hoe dat zou moeten werken.

De bedoeling is dat ik op plek "A" (de telefoon) een PHP-script aanroep (dat op de NAS draait) waar ik als URL-parameter mijn data doorgeef.

Dus op de telefoon browse ik naar: http:\\IP-van-NAS\GeefDataDoor.php?data=HoiTest123

Vervolgens moet "iets" (ik denk de Websocket die ook op de NAS draait, dit is dan plek "B") deze data "oppakken" en doorsturen naar "C", namelijk de HTML die op de iPad geopend is (en ook op de NAS draait).

Dus de data gaat vanuit A, via B naar C.

@Rob: thanks!

Jou eerste idee had ik ook al bedacht. Niet zo mooi als jij het schetst, want ik wist niet van het bestaan van een RAM-disk. De reden dat ik mijn idee (met gewoon lompweg een bestandje wegschrijven + uitlezen) niet heb gedaan is inderdaad vanwege het "slechte" voor de hardddisk(s)!

Kan ik het niet maken door alleen maar gebruik te maken van Websockets? Dat bijvoorbeeld in dat script dat constant draait ook iets zit dat de data opvangt van GeefDataDoor.php?
 
Rob Doemaarwat

Rob Doemaarwat

04/11/2021 07:43:42
Quote Anchor link
Het protocol van een WebSocket is vrij complex. Dus waarom moeilijk doen als het ook makkelijk kan ;-)

In websockets.php voeg je dit toe:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

//initialisatie

$port = 8085; //poort voor "interne communicatie"
print("Start listening on port $port\n");
$socket = socket_create_listen($port);
socket_set_nonblock($socket);

//in de while(true) loop:

if($client = socket_accept($socket)){ //we hebben een request
  $request = socket_read($client,10000);
  //dit is je rauwe request data
  //afhankelijk van wat je opstuurt krijg je hier dus wat "overhead" mee
  //doe een print($request) om te kijken hoe je je data hier weer uit peutert

}

?>


Vanaf je telefoon kun je nu twee dingen doen:

- Je roept je NAS op poort 8085 direct aan (als dat firewall technisch mag), dus http://IP-van-NAS:8085/websockets.php?data=HoiTest123 . In de $request uit bovenstaand stukje code vind je nu een complete HTTP request (inclusief headers) terug. Je moet de "data" er dus zelf nog uit peuteren.

- Je roept GeefDataDoor.php aan, en roept van daaruit websockets.php aan. Als je dat ook weer met een socket doet, dan bepaal je zelf het "protocol", en heb je dus geen last van "overhead" (de data die je meegeeft is de data die je aan de andere kant ontvangt).

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?php

$socket
= socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$length = strlen($data = $_GET['data'] ?? null);
socket_sendto($socket,$data,$lengt,0,'127.0.0.1',8085); //localhost als ze op dezelfde NAS draaien
socket_close($socket);

?>


De $request in de websockets.php zal nu precies de $data zijn die je hierboven doorgaf = direct bruikbaar.
 
Sjef dresen

sjef dresen

04/11/2021 09:28:12
Quote Anchor link
@Rob

Bedankt voor je uitbreide antwoord.
Het werkt al een beetje :-)

Met onderstaand script komen er geen foutmeldingen, maar zo te zien komt er ook niets aan bij het socket dat ik op de NAS laat draaien
via php -q websockets.php


Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

  ini_set('display_errors', '1');
  ini_set('display_startup_errors', '1');
  error_reporting(E_ALL);

  $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
  $length = strlen($data = $_GET['data'] ?? null);
  socket_sendto($socket,$data,$length,0,'192.168.1.202',8085);  //localhost als ze op dezelfde NAS draaien
  socket_close($socket);

  echo $data;

?>


(ook met 127.0.0.1 werkt het niet)


En als ik het "rechtstreeks" doe via http://192.168.1.202:8085/websockets.php?data=TestData dan komt de data wél aan.

Maar dan gaat er vervolgens iets mis:

Quote:
Warning: socket_write(): unable to write to socket [32]: Broken pipe in /volume1/web/websockets.php on line 48


Ook merk ik dat ik alleen de data kan sturen met bovenstaande link als de HTML pagina "luistert" naar het websocket.


Dit is het hele websocket.php script:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php

//initialisatie

$gestuurdeData = "";

$port2 = 8085; //poort voor "interne communicatie"
print("Start listening on port $port2\n");
$socket = socket_create_listen($port2);
socket_set_nonblock($socket);



$address = '192.168.1.202';
$port = 12345;

// Create WebSocket.
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($server, $address, $port);
socket_listen($server);
$client = socket_accept($server);

// Send WebSocket handshake headers.
$request = socket_read($client, 5000);
preg_match('#Sec-WebSocket-Key: (.*)\r\n#', $request, $matches);
$key = base64_encode(pack(
    'H*',
    sha1($matches[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
));

$headers = "HTTP/1.1 101 Switching Protocols\r\n";
$headers .= "Upgrade: websocket\r\n";
$headers .= "Connection: Upgrade\r\n";
$headers .= "Sec-WebSocket-Version: 13\r\n";
$headers .= "Sec-WebSocket-Accept: $key\r\n\r\n";
socket_write($client, $headers, strlen($headers));

// Send messages into WebSocket in a loop.
while (true) {
    sleep(1);
    $content = 'Now: ' . time();
    $response = chr(129) . chr(strlen($content)) . $content . $gestuurdeData;
    socket_write($client, $response);


//in de while(true) loop:

if($client2 = socket_accept($socket)){ //we hebben een request
  $request = socket_read($client2,10000);
  //dit is je rauwe request data
  //afhankelijk van wat je opstuurt krijg je hier dus wat "overhead" mee
  //doe een print($request) om te kijken hoe je je data hier weer uit peutert


  $gestuurdeData = $request;

  $message = "_test_" . $request;
 
  $a = fopen("websocket_dump.txt", "a+");
  fputs($a, $name . " / " . $message . "\r\n");
  fclose($a);

  print($request);

}


}


?>



Ik heb het idee dat ik er bijna ben, maar dit is toch nét iets te moeilijk voor me om dit alleen op te lossen.
Dus ik zou heel blij zijn als ik nog eens een beroep mocht doen op jou/jullie expertise.

Wederom bedankt!
 
Rob Doemaarwat

Rob Doemaarwat

04/11/2021 10:10:58
Quote Anchor link
Ik vermoed dat het aan het feit ligt dat ik vergeten was om de socket ook weer te sluiten (waardoor ie bij een volgende call nog open staat). Na regel 49 moet je daarvoor invoegen:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
socket_close($client2);
(en daarvoor evt. nog een socket_write() als je nog wat terug wilt zeggen).

Dat ie alleen werkt als je "luistert" komt vermoedelijk omdat anders je while(true) loop niet (door) loopt. Waarschijnlijk geeft je socket_write dan een error/timeout. Kun je in de logs op je NAS iets terug vinden?
 
Sjef dresen

sjef dresen

04/11/2021 10:48:41
Quote Anchor link
Hmzzz, ik ben alweer iets verder :-)
Het versturen vanuit "SendToSocket.php" heb ik nu werkend met deze code:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php

  ini_set('display_errors', '1');
  ini_set('display_startup_errors', '1');
  error_reporting(E_ALL);

  //$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
  //$length = strlen($data = $_GET['data'] ?? null);
  //socket_sendto($socket,$data,$length,0,'192.168.1.202',8085);  //localhost als ze op dezelfde NAS draaien 8085
  //socket_close($socket);



  $data = $_GET['data'];
  echo $data;


function
send_data($_remote_ip, $_remote_port, $_message)
{

    if (preg_match('/^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/', $_remote_ip)) {
        //  echo 'IP ist gültig.';
    } else {
        $_remote_ip = gethostbyname($_remote_ip);
    }

    if (!($sock = socket_create(AF_INET, SOCK_STREAM, 0))) {
        $errorcode = socket_last_error();
        $errormsg = socket_strerror($errorcode);
        die("Couldn't create socket: [{$errorcode}] {$errormsg} \n");
        $log_update = mysql_query("INSERT INTO `log`(`message`) VALUES ('" . $errormsg . "')");
    }

    echo "Socket created \n";
    //Connect socket to remote server
    if (!socket_connect($sock, $_remote_ip, $_remote_port)) {
        $errorcode = socket_last_error();
        $errormsg = socket_strerror($errorcode);
        die("Could not connect: [{$errorcode}] {$errormsg} \n");
        $log_update = mysql_query("INSERT INTO `log`(`message`) VALUES ('" . $errormsg . "')");
    }

    echo "Connection established \n";
    //Send the message to the server
    if (!socket_send($sock, $_message, strlen($_message), 0)) {
        $errorcode = socket_last_error();
        $errormsg = socket_strerror($errorcode);
        die("Could not send data: [{$errorcode}] {$errormsg} \n");
        $log_update = mysql_query("INSERT INTO `log`(`message`) VALUES ('" . $errormsg . "')");
    }

    echo "Message send successfully \n";
    //$log_update = mysql_query("INSERT INTO `log`(`message`) VALUES ('".$_remote_ip."')");
}


send_data("192.168.1.202", 8085, $data);


?>


Als ik dit aanroep via: http://192.168.1.202/SendToSocket.php?data=test123
Dan komt in de browser (op de telefoon dus) als output:

Quote:
test123Socket created Connection established Message send successfully


En in het Putty-venster (NAS) staat dan:

Quote:
Start listening on port 8085
test123PHP Warning: socket_write(): unable to write to socket [32]: Broken pipe in /volume1/web/websockets.php on line 50


De data ("test123") komt nu dus aan. Maar dus ook meteen een foutmelding erachteraan.

Het script "websockets.php" is nu zo:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?php

//initialisatie

$gestuurdeData = "";

$port2 = 8085; //poort voor "interne communicatie"
print("Start listening on port $port2\n");
$socket = socket_create_listen($port2);
socket_set_nonblock($socket);



$address = '192.168.1.202';
$port = 12345;

// Create WebSocket.
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($server, $address, $port);
socket_listen($server);
$client = socket_accept($server);

// Send WebSocket handshake headers.
$request = socket_read($client, 5000);
preg_match('#Sec-WebSocket-Key: (.*)\r\n#', $request, $matches);
$key = base64_encode(pack(
    'H*',
    sha1($matches[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
));

$headers = "HTTP/1.1 101 Switching Protocols\r\n";
$headers .= "Upgrade: websocket\r\n";
$headers .= "Connection: Upgrade\r\n";
$headers .= "Sec-WebSocket-Version: 13\r\n";
$headers .= "Sec-WebSocket-Accept: $key\r\n\r\n";
socket_write($client, $headers, strlen($headers));

// Send messages into WebSocket in a loop.
while (true) {
    sleep(1);
    $content = 'Now: ' . time();
    $response = chr(129) . chr(strlen($content)) . $content . $gestuurdeData;
    socket_write($client, $response);


//in de while(true) loop:

if($client2 = socket_accept($socket)){ //we hebben een request
  $request = socket_read($client2,10000);
  //dit is je rauwe request data
  //afhankelijk van wat je opstuurt krijg je hier dus wat "overhead" mee
  //doe een print($request) om te kijken hoe je je data hier weer uit peutert


  socket_write($client2, "hallo");
  socket_close($client2);

  $gestuurdeData = $request;

  $message = "_test_" . $request;
 
  $a = fopen("websocket_dump.txt", "a+");
  fputs($a, $name . " / " . $message . "\r\n");
  fclose($a);

  print($request);

}




//http://192.168.1.202:8085/websockets.php?data=testData


}

?>



Er is voor dit:

Quote:
Dat ie alleen werkt als je "luistert" komt vermoedelijk omdat anders je while(true) loop niet (door) loopt. Waarschijnlijk geeft je socket_write dan een error/timeout.


Niet een makkelijke oplossing te verzinnen? Of moet die code is in DIE loop zitten?


Edit:

En thanks weer ;-)
Gewijzigd op 04/11/2021 10:50:08 door sjef dresen
 
- Ariën  -
Beheerder

- Ariën -

04/11/2021 13:21:31
Quote Anchor link
Gebruik je nog mysql_query()?
Oei, oei, oei!

Die functie is inmiddels verouderd en afgeschreven. Ik raad met klem aan om MySQLi te gebruiken, of PDO. Maar MySQLi is het makkelijkste qua ombouw. Check wel even goed de manuals van php.net, want de notatie van die functies is in enkele gevallen afwijkend dan enkel een 'i' erbij te zetten.
Gewijzigd op 04/11/2021 13:23:14 door - Ariën -
 
Sjef dresen

sjef dresen

04/11/2021 13:37:01
Quote Anchor link
@Ariën:

Thanks voor de tip!

Maar ik gebruik dit zelf niet. Dit zit "standaard" in het voorbeeld dat ik gebruik.

Een goed moment om dat allemaal als commentaar te maken of zelfs uit te vegen. Dankje!
 
Rob Doemaarwat

Rob Doemaarwat

04/11/2021 16:40:07
Quote Anchor link
Die foutmelding "on line 50" kan niet helemaal kloppen met de code die daaronder staat. Daar is regel 50 nl een commentaar regel. Kijk even op welke regel het precies fout gaat.

Bij mij werkt dit overigens gewoon (als ik even het WebSocket deel er uit laat. Ik krijg alleen de foutmelding "Undefined variable: name in ... on line 62" (en dat klopt - dat die niet bekend is).
 
Sjef dresen

sjef dresen

04/11/2021 16:52:14
Quote Anchor link
Ah, mijn excuses!
Ik had wat (voor hier onnodig) commentaar uit het script verwijderd... Daarom zijn die regels natuurlijk opgeschoven...
Chips! Daar zal ik de volgende keer beter rekening mee houden.

Op regel 50 gebeurt dit:

socket_write($client, $response);

Hier werkt het ook als ik beide "onderdelen" los van elkaar test.
Maar helaas moet ik ze samen kunnen gebruiken anders krijg ik het niet werkend wat ik wil :-)

(variabele "name" heb ik ondertussen ook eruit gehaald)
 
Rob Doemaarwat

Rob Doemaarwat

04/11/2021 17:24:19
Quote Anchor link
En als je die socket_write() gewoon weg laat (geen response geven, maar gewoon direct een socket_close()?

En als je het "rechtstreeks" via een browser doet ( http://192.168.1.202:8085/websockets.php?data=TestData )?
 
Sjef dresen

sjef dresen

04/11/2021 18:37:14
Quote Anchor link
Er zijn 2 socket_write() he :-)
Namelijk die "van jou" en die wat ik al had om naar de iPad te sturen zegmaar.

Als ik die laatste weglaat dan wordt de HTML/iPad dus niet geupdate. Verder werkt dan wél alles.
Dus de data komt dan wel netjes aan in m'n Putty-scherm...

Het werkt dan zowel met SendToSocket.php als "rechtstreeks" in de browser zoals je voorstelde.
Maar het laatste traject -waarbij de data uiteindelijk bij de iPad komt- werkt dan dus niet (omdat ik socket_write($client, $response); heb weggelaten)
 
Rob Doemaarwat

Rob Doemaarwat

04/11/2021 19:37:53
Quote Anchor link
Aha - ik zat zelf inderdaad ook verkeerd te kijken. Dus het probleem treedt op bij het schrijven naar de WebSocket.

Ik heb het hele gebeuren even nagebouwd. Het gaat fout op die socket_write(), maar het probleem zit al in de regel daarboven:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
    $response = chr(129) . chr(strlen($content)) . $content . $gestuurdeData;
    socket_write($client, $response);

Geen idee hoe de opbouw van het protocol is, maar je geeft daar dus de lengte van de $content (die op deze manier dus nooit meer dan 255 bytes kan zijn!). Op het moment dat $gestuurdeData niet meer leeg is loopt het protocol in de soep, omdat die niet meegenomen wordt in de lengte.

De oplossing is dus:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
    $response = chr(129) . chr(strlen($content . $gestuurdeData)) . $content . $gestuurdeData;
    socket_write($client, $response);

Daar kunnen waarschijnlijk nog wat "oppoets slagen" overheen, maar bij mij werkt het complete verhaal zo.

Ik heb nog wel dit gedaan (ook om de $content binnen de 255 karakters te houden):
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
  preg_match('/data=(.*?) /',$request,$match);
  $gestuurdeData = $match[1]; //de data
Gewijzigd op 04/11/2021 19:39:15 door Rob Doemaarwat
 



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.