【问题标题】:QTcpSocket in a thread doesn't always send data instantly线程中的 QTcpSocket 并不总是立即发送数据
【发布时间】:2015-12-17 19:47:03
【问题描述】:

我有一个服务器,它在一个单独的线程中处理每个客户端连接。为了测试它,我实现了一个简单的“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 是一个连续的流,但我没想到在没有负担的 LAN 上传输几个字节需要几分钟。有趣的是,客户端可以立即正确地发送所有内容,它从不等待进一步的写入将它们粘合在一起。

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

一旦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)";
}

我知道,这不是处理线程的best practice,我只是使用“Qt4方法”快速熟悉QTcpSocket。

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

QSocketNotifier:不能从 另一个线程

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

确实,如果我将套接字的实例化到构造函数中,我会得到

QObject:无法为不同的父级创建子级 线。 (父线程是QTcpSocket(0x2c54a80),父线程是 QThread(0x10776d0),当前线程为TcpThread(0x2c53c18)

这是我以前没有得到的。但是,即使在构造函数中进行了实例化,我的代码的工作方式也完全相同:消息有时会立即发送,有时会粘在下一条消息上并与下一条消息一起发送,无论两者之间经过多长时间。

我做错了什么?

【问题讨论】:

标签: c++ multithreading qt tcp


【解决方案1】:

使用带有QTcpSocket 的线程毫无意义。它是异步 API,因此使用线程的收益很小(问题可能仅在您有其他耗时的作业阻塞事件循环时)。

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

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

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

所以一般来说,您的代码中有很多错误。我会建议放弃QThread 或至少再次阅读该文章以理解它并应用它的建议(它是在 Qt5 发布之前编写的,因此您声称“Qt4 风格”是无效的)。

【讨论】:

  • 我知道我可以异步使用QTcpSocket,我只是想使用线程,因为数据量可能非常大,对数据的评估可能非常昂贵。因此,我想尝试在线程中进行通信及其评估。当然,不使用线程进行通信本身,仅使用线程来评估数据也可能被证明是一个不错的选择......
  • @vsz 没关系,传递多少数据。因为回调是在收到数据后调用的。您仍然可以在另一个线程中处理数据。最昂贵的操作是创建一个QByteArrayread 方法将执行此操作。这只是内存分配+复制。
  • QTSPSocket 以块的形式向您发送数据(您无法控制此块的大小),因此您仍在处理非常小的数据块我不打算处理这些块太费时间了!在没有线程的情况下执行此操作的另一个优点是您将正确执行此操作,稍后将准备好的解决方案移动到线程将只需几行。
猜你喜欢
  • 2017-06-23
  • 2011-12-06
  • 2011-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-16
  • 2021-08-30
相关资源
最近更新 更多