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

Posted

技术标签:

【中文标题】PHP socket_write 失败仅在下次调用时返回错误【英文标题】:PHP socket_write failure only returns error on next call 【发布时间】:2020-11-21 18:43:49 【问题描述】:

我正在尝试通过 php 套接字发送数据,客户端是 Arduino 设备,当我多次发送时它可以接收数据,但是如果我重置客户端(Arduino 设备),它会在几秒钟内重新启动,它说它已连接到 PHP 套接字,然后当我想通过 socket_send() 再次发送数据时它会静默失败,PHP socket_send() 不会在第一个实际错误时返回错误,只是我第二次尝试(失败) ,只有这样它才返回错误(“发送零字节”)。收到此错误后,我创建另一个socket_accept() 并成功发送消息。

什么可能导致这种情况?我希望它能够正确检测到丢失的连接,以便在需要时重新发送数据。

感觉就像它发送数据到一个旧的连接,只是在第二次尝试时才意识到,这可能吗?

socket_select() 可以解决这个问题吗?我很难理解它的作用。

澄清:如果客户端重新启动并再次连接,则向其发送数据返回int,然后返回falsefalsefalse(除非我取消设置$accept 并再次执行socket_accept() )。如果客户端保持离线,则发送总是返回intintintint 是发送的字符串的大小。

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

【问题讨论】:

当您说“它默默地失败”时,@socket_send( 的确切结果是什么(请为 var_dump)? socket_select 返回改变状态的套接字数量,所以 $checkExcept = [$accept];socket_select(null, null, $checkExcept); 应该在出错时返回 1。 我第一次尝试发送并且客户端断开连接时,它返回int(32)(字符串大小),只有当我再次调用它时,它才返回bool(false)。由于客户端将完全离线,这不可能是客户端问题,不是吗? 如果客户端重新启动并再次连接,则向其发送数据返回int,然后返回falsefalsefalse(除非我取消设置$accept并执行@ 987654347@ 再次)。如果客户端保持离线,则发送始终返回intintint 【参考方案1】:

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

@socket_send($accept, $message_to_send, strlen($message_to_send), MSG_EOR);
$dataSent = @socket_write($accept, '', 0);

    if ($dataSent === false) 
        unset($accept);
        // ...

编辑

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

在接收端,您只需解码消息 ($data) 并发送回$data['payload']strlen。客户端也可以验证它是否已收到所有这样的数据。为此,只需将$data['payload'] 中的strlen$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 正是这样做的 - 确保已接收到数据。也许我们在这里仍然缺少一些东西。

【讨论】:

这个基本上是另一个发送,对吧?这可能是一种解决方案,在真实数据之前发送一些虚拟数据以触发错误。它可以工作,但感觉就像我们做错了什么。我还将发布我最终使用的解决方案。谢谢。 @adrianTNT 我为此添加了另一种方法。请问您的客户是用什么语言写的?如果是 php,我也可以提供一些合适的客户端代码。 这是一个ESP32 wifi板,用Arduino语言编码,是C/C++语言。我发现它在等待“好的”回复/确认时效果很好,我可能也会检查返回的字节数,正如你在代码中所说的那样。【参考方案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);

【讨论】:

以上是关于PHP socket_write 失败仅在下次调用时返回错误的主要内容,如果未能解决你的问题,请参考以下文章

Spring CORS 仅在某些资源上失败

PHP time() 有时会关闭几秒钟,然后在下次调用时更正

PHP 致命错误:仅在 Eclipse 中调用未定义函数 json_decode()

颤振:异常:SocketException:仅在iOS模拟器上连接失败

仅在 iOS 移动 Safari 中上传图像时出现错误的 CORS 失败

Winsock 连接失败并显示 WSAEFAULT |仅在 Windows 11 上出现错误