【发布时间】: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)
这是我以前没有得到的。但是,即使在构造函数中进行了实例化,我的代码的工作方式也完全相同:消息有时会立即发送,有时会粘在下一条消息上并与下一条消息一起发送,无论两者之间经过多长时间。
我做错了什么?
【问题讨论】:
-
TcpThread::read_data()不会在与TcpThread::run()相同的线程中运行。 It is important to remember that a QThread instance lives in the old thread that instantiated it, not in the new thread that calls run(). This means that all of QThread's queued slots will execute in the old thread. Thus, a developer who wishes to invoke slots in the new thread must use the worker-object approach; new slots should not be implemented directly into a subclassed QThread. -
@thuga ;我已经知道构造函数在旧线程中运行,但即使这种方法有时也有效,我预计如果出现问题,则不会发送任何数据包。我将尝试工作对象方法,看看是否有帮助。
-
我没有在任何地方提到构造函数。我说的是你的位置。
-
我知道。我提到了构造函数,因为这也是在旧线程中运行的情况。
-
是的,但你仍然在
TcpThread::run()方法中做事。而且QTcpSocket不是线程安全的类,所以在不同的线程中使用相同的实例会导致一些不希望的事情发生。
标签: c++ multithreading qt tcp