我正在使用线程telnet服务器(每个连接一个线程),并且无法弄清楚如何摆脱valgrind错误 . 我把问题缩小到了删除tcpsocket的地方 .
我在QThread的run()方法中创建了QTcpSocket:
void TelnetConnection::run()
{
tcpSocketPtr = new QTcpSocket();
if (!tcpSocketPtr->setSocketDescriptor(socketDescriptor)) {
emit error(tcpSocketPtr->error());
return;
}
....
}
当我的应用想断开客户端时,我打电话:
void TelnetConnection::disconnectClient()
{
tcpSocketPtr->disconnectFromHost();
}
并且在socket断开连接时调用的插槽是:void TelnetConnection :: clientDisconnected()
{
tcpSocketPtr->deleteLater();
TelnetConnection::s_clientCount--;
QThread::quit(); // Exit ths event loop for this thread
}
所以,我试过1.删除clientDisconnected插槽中的QTcpSocket,但这会导致读/写不稳定 . (偶然崩溃)2 . 在clientDisconnected插槽中删除,但导致内存读/写错误3.在线程的exec循环后删除但仍然导致内存读/写错误4.在线程的exec循环后删除 - 并且全部错误消失了 .
从我读到的内容,如果在exec循环终止后调用deletelater,则会在删除线程时运行 . 因此,虽然这有效,但我认为这不是正确的方法 .
我尝试使用“this”作为父级创建QTcpSocket,但由于父级vs这种不匹配错误,我的信号连接失败 . (这将允许在线程销毁时删除QTcpSocket) .
解决这个问题的正确方法是什么?
1 回答
你的问题几乎完全来自重新实现
QThread
. 不要这样做 . 将所有功能放入QObject
,然后使用moveToThread()
将其移至裸QThread
. 如果您只通过信号插槽连接从外部访问您的对象,那么您将立即完成 .首先,我总是将
TelnetConnection
的一些实例称为telnetThread
. 这只是为了让我明白我在谈论什么线程 .到目前为止,您显示的代码中的错误是:
run()
方法中调用emit error(tcpSocketPtr->error())
. 它是从telnetThread
调用的,与信号所在的QObject
不同:它位于telnetThread->thread()
.run()
方法在telnetThread
线程内执行 . 但是由moc生成的信号的实现预计会在你实例化的任何线程中调用QThread
,即telnetThread->thread()
,并且该线程 can never be equal 将被执行run()
. 基本上,有点混乱,以下不变量持有:tcpSocketPtr
上调用方法,生成telnetThread
,来自另一个线程中执行的插槽 . 以下是:telnetThread
上声明的所有插槽都在telnetThread
本身的不同线程中执行!因此,disconnectClient
的主体在GUI线程中执行,但它直接在tcpSocketPtr
上调用方法 .以下是一种方法 . 它侦听端口8023. ^ D结束连接 . 接收大写
Q
后跟Enter / Return将干净地关闭服务器 .Introduction
注意:此示例已重构,现在已正确删除最后一个服务器 .
我们需要注意确保事情干净利落 . 注意,只需
quit()
正在运行QCoreApplication
就可以了,总结会自动发生 . 因此,所有对象最终都被破坏和释放,没有任何东西应该崩溃 . 线程和服务器从析构函数向控制台发出诊断消息 . 这种方式显然会被删除 .该代码支持Qt 4和Qt 5 .
StoppingThread
向
QThread
添加缺少的行为 . 通常,当您破坏正在运行的线程时,会收到警告消息和崩溃/未定义的行为 . 这个类在被破坏时告诉线程's event loop to quit and waits for the thread to actually finish. It'就像QThread
一样,除了它在破坏时不会做愚蠢的事情 .ThreadedQObjectDeleter
当其线程被销毁时删除给定的
QObject
. 当线程在逻辑上拥有其对象时很有用 . 此逻辑所有权不是父子所有权,因为线程和逻辑拥有的对象存在于不同的线程(!)中 .构造函数是私有的,并提供了工厂方法 . 这是为了在免费商店(例如堆)上强制创建删除器 . 在堆栈上创建删除器可能是一个错误,因此该模式使用编译器来防止它发生 .
该对象尚未被移动到指定的线程,否则删除器的构造将受到竞争条件的影响 - 该对象可能已经在线程内自行删除 . 这个先决条件是断言 .
ServerFactory
在调用
newConnection
插槽时生成新的服务器实例 . 构造函数传递给客户端QObject
的QMetaObject
来创建 . 因此,这个类可以构造"any" desiredQObject
而无需使用模板 . 它创建的对象只有一个要求:它必须有
Q_INVOKABLE
构造函数,第一个参数为QTcpSocket*
,第二个参数为QObject *parent
. 它生成的对象是在父级设置为nullptr
的情况下创建的 .套接字的所有权将传输到服务器 .
ThreadedServerFactory
为每个服务器创建一个专用的StoppingThread,并将服务器移动到该线程 . 否则表现得像ServerFactory . 螺纹由工厂拥有,并在工厂被破坏时妥善处理 .
服务器的终止退出线程的事件循环,从而完成线程 . 已完成的线程已删除 . 在终止服务器之前被破坏的线程将删除现在悬空的服务器 .
TelnetServer
实现一个简单的telnet服务器 . 该接口由一个可调用的构造函数组成 . 构造函数使用套接字并将套接字连接到内部插槽 . 功能非常简单,类只响应来自套接字的
readyRead
和disconnected
信号 . 断开连接后,它会自行删除 .这不是真正的telnet服务器,因为telnet协议不是那么简单 . 碰巧telnet客户端将使用这种愚蠢的服务器 .
main()
main函数创建服务器,服务器工厂,并将它们连接在一起 . 然后它告诉服务器监听任何地址,端口8023上的连接,并启动主线程的事件循环 . 监听服务器和工厂位于主线程中,但所有服务器都存在于自己的线程中,您可以在查看欢迎消息时轻松查看 . 支持任意数量的服务器 .