无法从套接字读取数据(卡住了)

8

我正在使用PHP连接到本地的C++套接字服务器,以在Web应用程序和一些守护进程之间保持状态。 我可以向套接字服务器发送数据,但无法从中接收; 它只会在socket_read()上阻塞并无限期地挂起。 我是否忘记了一些愚蠢的东西(例如NULL字符或不同组合的换行符)? 下面是PHP代码:

socket_connect($sock, $addr, $port); 
socket_write($sock, 'Hello world');
$str = '';
while($resp = socket_read($sock, 1000))
    $str .= $resp;
socket_close($sock);
die("Server said: {$str}");

下面是与套接字服务器相关的部分(注意,<<>>操作符被重载):

std::string data;
sock >> data;
sock << data << std::endl;

>>调用Socket::recv(std::string&)时,它会接收数据;而当>>调用Socket::send(const std::string&)时,它会发送数据。

在telnet中这很好运行,但PHP似乎不太友好。欢迎提出任何想法/建议。


1
你在使用哪个C++的socket库,还是自己创建的(*nix或Windows)?另外,出现了什么错误?它只是在sock >> data上永远阻塞,还是发生了更多的事情?另外一点,我已经有一段时间没有使用PHP与C++服务器通信了,但是你的服务器是否向套接字发送终止信号,以便告诉PHP停止读取?你的循环将在PHP中持续读取,直到服务器告诉它套接字已经完成发送信息;这不是实时更新(因为它在完全收集并存储在变量中的数据之后才打印数据)。 - RageD
这可能只是你的代码缺少了,但是你是否先使用socket_create函数创建了$sock对象? - netcoder
另外,我在你的代码中没有看到这一点,但是当你的循环完成时,你需要打印 $str。 - RageD
哎呀,我不小心在代码中漏掉了 socket_create() - 已经更新。我应该早点澄清,抱歉 - 这是在 *NIX 上。没有明显的错误;服务器本身工作正常(我可以使用 telnet 并按预期工作) - 只是 PHP 的 socket_read() 无限期地挂起。套接字服务器只是我临时拼凑的,因为我找不到一个好的可用于工作的服务器(任何建议都将是很好的)。 - mway
5个回答

12
在PHP中,和大多数编程语言一样,默认情况下以阻塞模式打开套接字,除非使用socket_set_nonblock进行设置。这意味着,除非发生超时/错误或收到数据,否则socket_read将永远挂起。
由于您的终止字符似乎是一个新行,请尝试这个。
while($resp = socket_read($sock, 1000)) {
   $str .= $resp;
   if (strpos($str, "\n") !== false) break;
}
socket_close($sock);
die("Server said: $str");

确实,我知道它们默认是阻塞的,但我完全忘记了要么a)在响应写入后关闭套接字,要么b)中断循环。谢谢你提醒我。不过,如果我尝试在sock << data << "\0"之后使用NULL字符(理想情况下不是换行符),似乎不起作用- 如果我尝试在strpos($str, "\0")之后捕获它,它无法检测到。有什么想法吗? - mway
1
与C或C++不同,PHP不会在字符串末尾放置空字节,这就是为什么使用\0strpos函数在测试时会返回布尔值false。您必须提供另一个分隔符,使用超时(参见如何在套接字读取上设置超时?),或从服务器端关闭套接字。 - netcoder
好的,我也通过自己的测试意识到了这一点。感谢你的帮助! - mway
我认为代码可以稍微改进一下,只在$resp中搜索:if (strpos($resp, "\n") !== false) break; - jakob.j

2

TCP套接字通常会尝试将多个小的发送调用合并为一个数据包,以避免发送过多的数据包(Nagle算法)。这可能是在send()调用后无法接收到任何内容的原因。您需要使用TCP_NODELAY打开套接字以避免这种情况。


1
您可以在从套接字读取时尝试这个:

if(socket_recv ( $socket , $response , 2048 , MSG_PEEK)!==false)
{
   echo "Server said: $response";
}

注意:在使用MSG_PEEK的位置上使用MSG_WAITALL可能会导致套接字挂起(根据我曾经经历过的一次经验)。

0

在我看来,这是最佳解决方案:https://github.com/graze/telnet-client

$bytesRead = 0;
$out = '';
do { 
    $byte = socket_read($socket, 1);
    $bytesRead++;
    $out .= $byte;
    if (strpos($out, "\r\n") !== false) break;
} while (true);
echo $out;
socket_close($socket);

0

我刚刚解决了一个类似的问题,只不过是关于管道的(php的fopen是否与POSIX打开管道不兼容)。我不知道解决方案有多相似,但也许值得一看?我同意RageD的观点,你应该在读取和写入行之间插入cout来测试并查看C++是在sock >> data行还是在sock << data行上挂起...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接