为了账号安全,请及时绑定邮箱和手机立即绑定

PHP socket_write 失败仅在下次调用时返回错误

PHP socket_write 失败仅在下次调用时返回错误

PHP
杨魅力 2023-09-08 21:35:52
我正在尝试通过 PHP 套接字发送数据,客户端是 Arduino 设备,当我多次发送时它接收数据正常,但是如果我重置客户端(Arduino 设备),它会在几秒钟内重新启动,它说连接到 PHP 套接字,然后当我想再次发送数据时,它会socket_send()默默地失败,PHP在第一个实际错误时不会返回错误,只有在我第二次尝试(并失败)时,才会返回错误(“零”socket_send()发送的字节数”)。收到此错误时,我创建另一个错误socket_accept()并成功发送消息。什么可能导致这种情况?我希望它能够正确检测丢失的连接,以便我可以在需要时重新发送数据。感觉就像它向旧连接发送数据,并且只有在第二次尝试时才意识到,这可能吗?这可以通过 解决吗socket_select()?我很难理解它的作用。澄清:如果客户端重新启动并再次连接,则向其发送数据将返回int,然后返回false, false, false(除非我取消设置$accept并再次执行 a socket_accept())。如果客户端保持离线状态,则发送始终返回int, int, int。 int是发送的字符串的大小。$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Could not create socket\n");// reuse any existing open port to avoid errorsocket_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);
查看完整描述

2 回答

?
鸿蒙传说

TA贡献1865条经验 获得超7个赞

我进行了一些实验并能够重现该问题。这是我的解决方案(也许不是最好的方法,但有效):


@socket_send($accept, $message_to_send, strlen($message_to_send), MSG_EOR);

$dataSent = @socket_write($accept, '', 0);


    if ($dataSent === false) {

        unset($accept);

        // ...

编辑

我想出了一个更简洁的解决方案。我们只是做函数socket_send应该做的事情。返回false或int接收到的字节数。


在接收端,您只需解码消息 ( $data) 并发strlen回$data['payload']。客户端也可以验证它是否已收到这样的所有数据。为此,只需与strlen进行比较即可。如果你真的很偏执,你也可以实现一些校验和。$data['payload']$data['length']


服务器使用的代码send而不是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;

}

尽管这可行,但我认为 TCP 正是这样做的——确保数据已被接收。也许我们仍然缺少一些东西。


查看完整回答
反对 回复 2023-09-08
?
湖上湖

TA贡献2003条经验 获得超2个赞

这就是我最终使用的,看起来效果很好。它等待来自客户端的回复/确认,如果没有收到确认,则message_to_send保留,连接被清除,在下一个循环中它重新连接并重试发送现有的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);

}


查看完整回答
反对 回复 2023-09-08
  • 2 回答
  • 0 关注
  • 108 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信