skip to Main Content

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 socketn");

// 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 socketn");

$result = socket_listen($socket) or die("Could not set up socket listenern");

    
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 messagern"
    $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);

2

Answers


  1. Chosen as BEST ANSWER

    This is what I ended up using and it seems to work nicely. It waits for a reply/confirmation from client, if no confirmation received, message_to_send is preserved, connection is cleared, on next loop it reconnects and retries to send the existent message_to_send.

    $total_data_sent = @socket_send($accept, $message_to_send, strlen($message_to_send), MSG_EOR);
    
    // set a timeout for waiting a reply, in this case 500 milliseconds
    socket_set_option($accept, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>0, "usec"=>500000));
    
    // read a reply, which should be "ok" (text sent by client)
    $reply = socket_read($accept, 1024);
    
    // if reply was not "ok", then unset the connection and will reconnect on next loop
    if($reply != "ok"){
        unset($accept);
    }
    
    // if reply was "ok", then clear this message as it was successfuly sent
    if($reply == "ok"){
        unset($message_to_send);
    }
    

  2. 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);
            // ...
    

    edit

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search