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
,然后返回false
、false
、false
(除非我取消设置$accept
并再次执行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 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
,然后返回false
、false
、false
(除非我取消设置$accept
并执行@ 987654347@ 再次)。如果客户端保持离线,则发送始终返回int
、int
、int
。
【参考方案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
函数应该做的事情。返回false
或int
接收的字节数。
在接收端,您只需解码消息 ($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 失败仅在下次调用时返回错误的主要内容,如果未能解决你的问题,请参考以下文章
PHP time() 有时会关闭几秒钟,然后在下次调用时更正
PHP 致命错误:仅在 Eclipse 中调用未定义函数 json_decode()
颤振:异常:SocketException:仅在iOS模拟器上连接失败