QLocalSocket循环发数据至QLocalServer仅接收首条问题排查
我之前遇到一个头疼的问题:用QLocalSocket向QLocalServer循环发送5条数据,服务器居然只能接收到第一条;但要是在客户端每次发送之间加个1毫秒的延迟(比如QTest::qWait(1)),服务器就能正常接收全部数据。先贴一下我的客户端和服务端代码:
客户端代码(client.cpp)
#include "client.h" #include "QDataStream" #include <QTest> TestClient::TestClient() : m_socket{new QLocalSocket(this)}{ m_socket->connectToServer("TestServer"); if (m_socket->waitForConnected(1000)) { qDebug("socket Connected!"); } connect(m_socket, &QLocalSocket::readyRead, this, &TestClient::onNewData); } void TestClient::onNewData() { qCritical() << "data received from server"; } void TestClient::sendDataToServer() { QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_5_10); QString testString = "test data"; out << quint32(testString.size()); out << testString; m_socket->write(block); m_socket->flush(); } void TestClient::startClient() { for(int i = 0; i < 5; i++) { //QTest::qWait(1); //取消注释后可正常运行 sendDataToServer(); } }
服务端代码(server.cpp)
#include "server.h" TestServer::TestServer() : m_server{new QLocalServer(this)} { QLocalServer::removeServer("TestServer"); if (!m_server->listen("TestServer")) { qCritical() << "couldn't connect to server"; } connect(m_server, &QLocalServer::newConnection, this, &TestServer::onNewConnection); } void TestServer::onNewConnection() { m_socket = m_server->nextPendingConnection(); connect(m_socket, &QLocalSocket::readyRead, this, &TestServer::onNewData); connect(m_socket, &QLocalSocket::disconnected, m_socket, &QLocalSocket::deleteLater); } void TestServer::onNewData() { QLocalSocket* client = qobject_cast<QLocalSocket*>(sender()); client->readAll(); qCritical() << "data read by server"; }
我之前看到Qt文档说:readyRead()信号不会递归发射;如果在readyRead的槽函数里重新进入事件循环或者调用waitForReadyRead(),这个信号不会被重新发射(哪怕waitForReadyRead()返回true)。我一度以为是这个原因导致的问题,而且疑惑是不是只能加定时器解决?
先澄清:和readyRead的递归限制无关!
咱们先拆解问题根源:
- QLocalSocket本质是本地域套接字,和TCP一样属于流协议——流协议没有“数据包”的概念,数据是连续的字节流。当你在客户端循环快速调用sendDataToServer时,系统会把多次小数据合并成一个数据包发送(这是Nagle算法的优化,减少IO次数),5条数据会被打包成一个字节流一次性发送到服务端。
- 服务端的
onNewData槽里只调用了一次readAll(),第一次readyRead触发时,所有5条数据已经在服务端的缓冲区里了,所以一次readAll就读完了所有数据,后续自然不会再触发readyRead信号——看起来像是只收到了第一条,但实际上是所有数据被一次性读取了,只是日志只打印了一次而已。
你可以做个测试:在服务端onNewData里打印读取到的字节数,会发现第一次读取的长度是5条数据的总长度,而不是单条的长度。
解决方案:不用定时器也能完美解决!
方案1:正确处理流协议的粘包问题(强烈推荐)
你客户端其实已经做了一半:在每条数据前加了quint32的长度前缀,只是服务端没按照这个协议解析。修改服务端的onNewData逻辑,按照协议逐步解析:
void TestServer::onNewData() { QLocalSocket* client = qobject_cast<QLocalSocket*>(sender()); static QByteArray buffer; buffer.append(client->readAll()); // 按照自定义协议解析:先读长度,再读对应内容 while (buffer.size() >= sizeof(quint32)) { QDataStream in(&buffer, QIODevice::ReadOnly); in.setVersion(QDataStream::Qt_5_10); quint32 strSize; in >> strSize; // 确保缓冲区有足够数据读取完整字符串 if (buffer.size() >= sizeof(quint32) + strSize) { QString testString; in >> testString; qCritical() << "data read by server:" << testString; // 移除已经解析完成的部分字节 buffer = buffer.mid(sizeof(quint32) + strSize); } else { // 数据不够完整,等待下一次readyRead break; } } }
不管数据是一次性到达还是分批到达,这个逻辑都能正确解析出每条数据,完全不需要加延迟。
方案2:禁用Nagle算法(仅特殊场景使用)
如果你一定要让每次发送的数据单独传输,可以在客户端socket上禁用Nagle算法:
m_socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
这样每次write的数据会立即发送,不会被合并,但会增加本地套接字的IO开销,一般不推荐这种方式。
方案3:用waitForBytesWritten阻塞等待(不推荐GUI程序)
在客户端每次sendDataToServer后调用:
m_socket->waitForBytesWritten();
等待数据写入系统缓冲区后再发送下一条,也能避免粘包,但这是阻塞调用,会卡住事件循环,不适合GUI应用。
为什么加1毫秒延迟能工作?
其实就是延迟给了系统足够的时间把前一次的数据发送出去,避免了数据合并,所以每条数据单独到达服务端,每次readyRead读取一条,日志就会打印5次——但这只是绕开了问题,不是根本解决方法。
内容的提问来源于stack exchange,提问作者Vencat

