线程中的 QTcpSocket 并不总是立即发送数据

QTcpSocket in a thread doesn't always send data instantly

本文关键字:数据 QTcpSocket 线程      更新时间:2023-10-16

我有一个服务器,在一个单独的线程中处理每个客户端连接。为了测试它,我实现了一个简单的"echo"服务:我在客户机中输入一条消息,它被发送到服务器,服务器将它发送回来,客户机显示它。

由于我只发送短数据包,因此在当前的测试中只使用单个写和读操作,因此从未有过任何分割数据包。

客户端使用write()readAll(),它一直工作完美(用数据包嗅探工具验证)

我几乎一直观察到的是,服务器每隔一秒才发送数据包。

例如:

  • 客户端发送"abc"
  • 服务器接收并返回。write()函数返回3.
  • 客户端没有收到任何消息。我检查封包嗅探器,没有发送数据包
  • 客户端发送"def",几秒钟,甚至几分钟后
  • 服务器接收并返回。write()函数返回3.
  • 客户端同时接收abc和def消息。90%的时间作为两个单独的数据包,10%的时间作为"abcdef"。
在非常罕见的情况下,一条消息就足够了。然而,大多数情况下,需要发送第二条消息来同时发送第一条消息和第二条消息。

客户端的调试输出示例,指示发送和接收的内容(注意客户端发送的所有内容都是手动输入的,并且在每个消息之间我至少等待10秒):

Client: 1
Client: 2
Server: 1
Server: 2
Client: 12345
Server: 12345
Client: abc
Client: def
Server: abcdef
Client: 123
Server: 123
Client: 456
Client: 789
Server: 456789
Client: a
Server: a
Client: b
Client: c
Server: b
Server: c

我知道TCP是一个连续的流,但我没有想到在一个无负载的局域网上,它应该花几分钟来传输几个字节。有趣的是,客户端可以立即正确地发送所有内容,它从不等待进一步的写入将它们粘合在一起。

由于客户端似乎工作完美,我怀疑我的线程有问题。

一旦QTcpServer接收到传入的连接,我启动一个新线程,将socketDescriptor传递给线程的构造函数。

TcpThread::TcpThread(int socketDescriptor, QObject *parent) : QThread(parent)
{
    this->socketDescriptor = socketDescriptor;
}
void TcpThread::run()
{
    if (!tcpSocket->setSocketDescriptor(socketDescriptor)) {
        qDebug() << tcpSocket->error();
        return;
    }
    connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(read_data()));
    while (tcpSocket->state() == QAbstractSocket::ConnectedState)
    {
        tcpSocket->waitForDisconnected(-1);
    }
}

void TcpThread::read_data()
{
    QByteArray data = tcpSocket->readAll();
    int nr = tcpSocket->write(data);
    qDebug() << data << " (" << nr << " bytes written)";
}

我知道,这不是处理线程的最佳实践,我只是使用"Qt4方法"来快速熟悉QTcpSocket。

有趣的是,我在每次调用write()时都会收到警告:

QSocketNotifier:不能启用或禁用套接字通知器另一个线程

我想知道为什么。我在我的线程的run()方法中创建了QTcpSocket的实例,所以它应该在同一个线程中,不是吗?

实际上,如果我将套接字的实例化移到构造函数中,我将得到

QObject:不能为父对象创建子对象线程。(父线程是QTcpSocket(0x2c54a80),父线程是QThread(0x10776d0),当前线程是TcpThread(0x2c53c18)

我以前没有得到。然而,即使在构造函数中进行了实例化,我的代码也完全相同:消息有时立即发送,有时与下一条消息粘在一起并发送,无论两者之间间隔多长时间。

我做错了什么?

使用带有QTcpSocket的线程是没有意义的。它是异步API,所以使用线程的收益是最小的(问题可能只有当你有其他耗时的任务阻塞事件循环时)。

另一个问题是您创建对象并将其分配给线程的方式。你给出了一个关于这个话题的完美链接,但你完全不理解它。

在QT中使用线程的简单规则:

  • 永远不要给自己分配线程
  • 你可以在线程之间移动对象,如果他们没有父对象(如果他们是内存结构树的根,整个三个被移动到新线程)
  • 设置父线程分配对象给父线程
  • 槽和信号的默认连接检测信号是从分配给接收器的不同线程发出的,因此在这种情况下,信号被包装并通过其事件循环传递给适当的线程。
  • 如果你在信号槽中使用值对象,你不需要额外的同步(锁和互斥锁)
  • 当你覆盖QThread::run()并使用信号和插槽时,你应该在该线程中运行事件循环,通过调用QThread::exec()或创建新的事件循环。

所以通常你的代码中会有很多错误。我建议放弃QThread,或者至少再次阅读那篇文章,理解它并应用它的建议(它是在Qt5发布之前写的,所以你所说的"Qt4风格"是无效的)。