PHP socket_write 仅失败 returns 下次调用时出错
PHP socket_write failure only returns error on next call
我正在尝试通过PHP套接字发送数据,客户端是Arduino设备,当我多次发送时它接收到数据正常,但是如果我重置客户端(Arduino设备),它几秒钟后重新启动,它说它连接到 PHP 套接字,然后当我想通过 socket_send()
再次发送数据时它静默失败,PHP socket_send()
不是在第一个实际错误上返回错误,仅在我第二次尝试(并失败)时,才返回 returns 错误(“已发送零字节”)。收到此错误后,我创建另一个 socket_accept()
并成功发送消息。
这可能是什么原因造成的?我希望它能够正确检测到丢失的连接,以便我可以在需要时重新发送数据。
感觉就像它向旧连接发送数据,并且在第二次尝试时才意识到,这可能吗?
socket_select()
可以解决这个问题吗?我无法理解它的作用。
澄清:如果客户端重新启动并再次连接,然后向其发送数据returns int
,则false
,false
,false
(除非我取消设置 $accept
并再次执行 socket_accept()
)。如果客户端保持离线,则发送总是returnsint
、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($accept, $message_to_send, strlen($message_to_send), MSG_EOR);
$dataSent = @socket_write($accept, '', 0);
if ($dataSent === false) {
unset($accept);
// ...
编辑
我想出了一个更巧妙的解决方案。我们只是做 socket_send
函数应该做的事情。 Return 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 确实如此 - 确保已接收到数据。也许我们还遗漏了一些东西。
这就是我最终使用的,它似乎工作得很好。
它等待来自客户端的 reply/confirmation,如果没有收到确认,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套接字发送数据,客户端是Arduino设备,当我多次发送时它接收到数据正常,但是如果我重置客户端(Arduino设备),它几秒钟后重新启动,它说它连接到 PHP 套接字,然后当我想通过 socket_send()
再次发送数据时它静默失败,PHP socket_send()
不是在第一个实际错误上返回错误,仅在我第二次尝试(并失败)时,才返回 returns 错误(“已发送零字节”)。收到此错误后,我创建另一个 socket_accept()
并成功发送消息。
这可能是什么原因造成的?我希望它能够正确检测到丢失的连接,以便我可以在需要时重新发送数据。
感觉就像它向旧连接发送数据,并且在第二次尝试时才意识到,这可能吗?
socket_select()
可以解决这个问题吗?我无法理解它的作用。
澄清:如果客户端重新启动并再次连接,然后向其发送数据returns int
,则false
,false
,false
(除非我取消设置 $accept
并再次执行 socket_accept()
)。如果客户端保持离线,则发送总是returnsint
、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($accept, $message_to_send, strlen($message_to_send), MSG_EOR);
$dataSent = @socket_write($accept, '', 0);
if ($dataSent === false) {
unset($accept);
// ...
编辑
我想出了一个更巧妙的解决方案。我们只是做 socket_send
函数应该做的事情。 Return 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 确实如此 - 确保已接收到数据。也许我们还遗漏了一些东西。
这就是我最终使用的,它似乎工作得很好。
它等待来自客户端的 reply/confirmation,如果没有收到确认,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);
}