Unix 套接字挂在 recv 上,直到我在任何地方放置/删除断点

Unix socket hangs on recv, until I place/remove a breakpoint anywhere

本文关键字:任何地 方放置 删除 断点 套接字 recv Unix      更新时间:2023-10-16

[TL;DR 版本:下面的代码在发布和调试模式下的第二个 recv() 调用上无限期挂起。在调试中,如果我在代码中的任何位置放置或删除断点,它会使执行继续并且一切正常]

我正在使用UNIX套接字编写简单的客户端-服务器通信。服务器处于C++,而客户端处于 python 状态。连接(本地主机上的TCP套接字)建立没有问题,但是当涉及到在服务器端接收数据时,它会挂起recv功能。以下是出现问题的代码:

bool server::readBody(int csock) // csock is the socket filedescriptor
{
int bytecount;
// protobuf-related variables
google::protobuf::uint32 siz;
kinMsg::request message;
// if the code is working, client will send false
// I initialize at true to be sure that the message is actually read
message.set_endconnection(true); 
// First, read 4-characters header for extracting data size
char buffer_hdr[5];
if((bytecount = recv(csock, buffer_hdr, 4, MSG_WAITALL))== -1)
::std::cerr << "Error receiving data "<< ::std::endl;
buffer_hdr[4] = '';
siz = atoi(buffer_hdr);
// Second, read the data. The code hangs here !!
char buffer [siz];
if((bytecount = recv(csock, (void *)buffer, siz, MSG_WAITALL))== -1)
::std::cerr << "Error receiving data " <<  errno  << ::std::endl;
//Finally, process the protobuf message
google::protobuf::io::ArrayInputStream ais(buffer,siz);
google::protobuf::io::CodedInputStream coded_input(&ais);
google::protobuf::io::CodedInputStream::Limit msgLimit = coded_input.PushLimit(siz);
message.ParseFromCodedStream(&coded_input);
coded_input.PopLimit(msgLimit);
if (message.has_endconnection())
return !message.endconnection();
return false;
}

从代码中可以看出,该协议是这样的,客户端将首先以 4 个字符的数组发送消息中的字节数,然后发送 protobuf 消息本身。第一个 recv 呼叫运行良好,不会挂起。然后,代码在第二个 recv 调用上挂起,该调用应该正在恢复消息的正文。

现在,对于有趣的部分。在发布模式下运行时,代码无限期挂起,我必须杀死客户端或服务器。无论是从我的IDE(qtcreator)运行它,还是在干净构建后从CLI运行它(使用cmake/g++),都没有关系。

当我在调试模式下运行代码时,它也在同一个 recv() 调用时挂起。然后,如果我在代码中的任何位置放置或删除断点(在该代码行之前或之后),它会再次启动并完美运行:服务器接收数据,并在返回 readBody 函数之前读取正确的 message.endconnection() 值。我必须放置以触发此行为的断点不一定是三重的。由于 readBody() 函数处于循环中(我的C++服务器等待来自 python 客户端的请求),在下一次迭代时,相同的行为再次发生,我必须在代码中的任何位置放置或删除断点,这不一定是触发的,以便通过该 recv() 调用。循环如下所示:

bool connection = true;
// server waiting for client connection
if (!waitForConnection(connectionID)) std::cerr << "Error accepting connection" << ::std::endl;
// main loop
while(connection)
{
if((bytecount = recv(connectionID, buffer, 4, MSG_PEEK))== -1)
{
::std::cerr << "Error receiving data "<< ::std::endl;
}
else if (bytecount == 0)
break;
try
{
if(readBody(connectionID))
{
sendResponse(connectionID);
}
// if client is requesting disconnection, break the while(true)
else
{
std::cout << "Disconnection requested by client. Exiting ..." << std::endl;
connection = false;
}
}
catch(...)
{
std::cerr << "Erro receiving message from client" << std::endl;
}
}

最后,如您所见,当程序从 readBody() 返回时,它会向客户端发送回另一条消息,客户端处理它并在标准输出中打印(python 代码工作,未显示,因为问题已经足够长)。从最后一个行为,我可以得出结论,协议和客户端代码是正常的。我试图在很多地方放置睡眠指令,看看这是否是时间问题,但它并没有改变任何东西。

我在谷歌和SO上搜索了类似的问题,但没有找到任何东西。帮助将不胜感激!

解决方案是不使用任何标志。用0调用recv作为标志,或者只使用read而不是recv

您正在请求不存在的数据的套接字。recv需要 10 个字节,但客户端只发送了 6 个字节。MSG_WAITALL明确指出,在流中有 10 个字节可用之前,调用应阻止。

如果不使用任何标志,则调用将在 6 处bytecount成功,这与MSG_DONTWAIT完全相同,没有非阻塞调用的潜在副作用。

我在 github 项目上做了测试,它有效。

解决方案是在 recv() 调用中将MSG_WAITALL替换为MSG_DONTWAIT。它现在工作正常。总而言之,它使 recv() 调用非阻塞,这使得整个代码工作正常。

然而,这仍然提出了许多问题,其中第一个问题是:为什么要使用这个奇怪的断点改变事物?

如果套接字首先阻塞,则可以假设这是因为套接字上没有数据。让我们在这里假设这两种情况:

  1. 套接字上没有数据,这就是阻塞 recv() 调用不起作用的原因。在相同情况下,将其更改为非阻塞 recv() 调用将触发错误。否则,protobuf 反序列化随后将失败,尝试从空缓冲区反序列化。但它没有...

  2. 套接字上有数据。那么,它到底为什么要阻止呢?

显然,关于 C 中的套接字,我有些不明白,如果有人对这种行为有解释,我会很高兴!