I am trying to send data by PHP sockets, the client is an Arduino device, it receives the data OK when I send it multiple times, but if I reset the client (Arduino device), it reboots in a few seconds, it says it connected to the PHP socket, then when I want to send data again by socket_send()
it fails silently, the PHP socket_send()
is not returning an error on first actual error, only the second time I try (and fail), only then it returns error ("zero bytes sent"). When this error is received, I create another socket_accept()
and successfully send the message.
What could cause this ? I want it to properly detect a lost connection so I can resend data if needed.
It feels like it sends data to an old connection and only realizes it on second try, is that possible ?
Can this be fixed by socket_select()
? I have trouble understanding what that does.
Clarification: If client restarts and connects again, then sending data to it returns int
, then false
, false
, false
(unless I unset the $accept
and I do a socket_accept()
again). If client remains offline, then the sending always returns int
, int
, int
.
int
being the size of the string sent.
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Could not create socket\n");
// reuse any existing open port to avoid error
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
$result = socket_bind($socket, $host, $port) or die("Could not bind to socket\n");
$result = socket_listen($socket) or die("Could not set up socket listener\n");
do{
if(!isset($accept)){
echo "\nwaiting for clients";
$accept = @socket_accept($socket) or die("Could not accept incoming connection");
echo "\nclient connected";
}
// memcached will return a message here like: "my message\r\n"
$message_to_send = $memcached->get('my_socket_message');
if($message_to_send!=''){
echo "\nsending: ".$message_to_send;
$total_data_sent = @socket_send($accept, $message_to_send, strlen($message_to_send), MSG_EOR);
// if data was not send (sent to an old connection ?!)...
// then clear $accept, so a new connection is accepted
// and keep the my_socket_message variable, so message is sent again
if($total_data_sent === false){
echo "\nSEND FAILED, will retry message: ".$message_to_send;
unset($accept);
} else {
$memcached->delete('my_socket_message');
}
}
} while (true);
I've experimented a bit and was able to reproduce the issue. Here is my solution for it (maybe not the best way, but works):
@socket_send($accept, $message_to_send, strlen($message_to_send), MSG_EOR);
$dataSent = @socket_write($accept, '', 0);
if ($dataSent === false) {
unset($accept);
// ...
I came up with a more neat solution. We just do what the socket_send
function is supposed to do. Return false
or int
number of received bytes.
On the receiving end you just decode the message ($data
) and send back the strlen
of $data['payload']
. The client can verify it has received all data like this too. To do so just compare strlen
of $data['payload']
with $data['length']
. If you really get paranoid you can implement some checksum aswell.
Code for Server use send
instead of socket_send
:
function send($client, $message) {
$messageLength = strlen($message);
$data = [
'length' => $messageLength,
'payload' => $message
];
$jsonData = json_encode($data);
try {
@socket_send($client, $jsonData, strlen($jsonData), MSG_EOR);
$response = @socket_read($client, 1024);
if (intval($response) !== $messageLength) {
return false;
}
} catch (Exception $e) {
return false;
}
return $response;
}
Even though this works, I thought TCP does exactly that - ensure the data has been received. Maybe we are still missing something here.