记一个QSharedPointer 和 QTcpsocket一起使用的断开连接导致段错误问题
写了一个简易的TCPserver,需要很多回调需要socket再回调内写数据,就需要保证回调执行的时候,socket不能被删除,所以采用智能指针老保证QTcpSocket 类的生命周期,加上本地管理,写了如下代码:
- Socket
“` C++
class SCPISocket : public QTcpSocket ,public QEnableSharedFromThis
{
Q_OBJECT
public:
explicit SCPISocket(qintptr sptr,QObject *parent = nullptr);
~SCPISocket();inline const QString & ID() const {return _id;}
inline const QSharedPointerdevice() const {return _device;} -
void writeCMD(QSharedPointer
- QTcpSocket{parent}
{
id = QUuid::createUuid().toString();
setSocketDescriptor(sptr);
connect(this,&SCPISocket::disconnected,this,&SCPISocket::onDisconnect);
connect(this,&SCPISocket::readyRead,this,&SCPISocket::readed);
}
SCPISocket::~SCPISocket()
{
}
void SCPISocket::init()
{
}
void SCPISocket::onDisconnect()
{
emit onClose(_id);
}
void SCPISocket::writeCMD(QSharedPointer& cmd,const std::function<QByteArray(const QJsonArray & )> & build) = sharedFromThis();
{
/* auto this
cmd->handler = [this_,build](const QSharedPointer& scmd){
auto status = scmd->status() SystemCMD::IN_finish;
auto result = scmd->result();
QByteArray ret;
if(!status){
ret.append(“0 \r\n”);
} else {
ret = build(result);
ret.append(“\r\n”);
}
auto ptr = this_.data();
QMetaObject::invokeMethod(ptr,this_,ret{
if(this_->state() QTcpSocket::ConnectedState){
this_->write(ret);
}
},Qt::QueuedConnection);
};
_device->write(cmd); */
}
void SCPISocket::readed()
{
}
& cmd,const std::function & build
= std::bind(buildRet,std::placeholders::_1));
signals:
void onClose(const QString & dev);
private slots:
void onDisconnect();
void init();
void readed();
private:
QString _id;
};
SCPISocket::SCPISocket(qintptr sptr, QObject *parent) - QTcpSocket{parent}
* Server
``` C++
class SCPISocket;
class RemoteSCPIServer : public QTcpServer
{
Q_OBJECT
public:
RemoteSCPIServer();
signals:
void newConnect(const QSharedPointer<SCPISocket> & socket);
protected:
void incomingConnection(qintptr handle);
};
class SCPIServer : public QObject
{
Q_OBJECT
public:
explicit SCPIServer(QObject *parent = nullptr);
void start(const QString & ip,quint16 port,const QSharedPointer<SystemDevice> & d );
void stop();
signals:
void scpiReq(const QString & cmd);
private slots:
void newConnect(const QSharedPointer<SCPISocket> & socket);
void onClose(const QString & dev);
private slots:
void doStart();
void doStop();
private:
QSharedPointer<SystemDevice> _device;
QHash<QString,QSharedPointer<SCPISocket >> _linkSocket;
QString _ip;
quint16 _port;
RemoteSCPIServer * _service = nullptr;
};
RemoteSCPIServer::RemoteSCPIServer()
{}
void RemoteSCPIServer::incomingConnection(qintptr handle)
{
auto socket = QSharedPointer<SCPISocket>::create(handle);// new RemoteSocket(handle);
emit newConnect(socket);
}
SCPIServer::SCPIServer(QObject *parent)
: QObject{parent}
{
}
void SCPIServer::start(const QString &ip, quint16 port,const QSharedPointer<SystemDevice> & d)
{
_ip = ip;
_port = port;
_device = d;
QTimer::singleShot(20,this,&SCPIServer::doStart);
}
void SCPIServer::stop()
{
QTimer::singleShot(0,this,&SCPIServer::doStop);
}
void SCPIServer::doStart()
{
if(_service) return;
_service = new RemoteSCPIServer();
connect(_service,&RemoteSCPIServer::newConnect,this,&SCPIServer::newConnect);
_service->listen(QHostAddress(_ip),_port);
_service->resumeAccepting();
}
void SCPIServer::doStop()
{
if(!_service){
for(auto it = _linkSocket.begin(); it != _linkSocket.end(); ++it){
it.value()->disconnectFromHost();
}
_linkSocket.clear();
_service->close();
_service->deleteLater();
}
_service = nullptr;
}
void SCPIServer::newConnect(const QSharedPointer<SCPISocket> &socket)
{
socket->setDevice(_device);
_linkSocket.insert(socket->ID(),socket);
connect(socket.data(),&SCPISocket::onClose,this,&SCPIServer::onClose);
}
void SCPIServer::onClose(const QString &dev)
{
_linkSocket.remove(dev);
}
</code></pre>
本来这就是一个很简单的智能指针管理的程序,默认Server里保留其计数,如果有lambda再其他线程使用,再lamba的结构体中同时保留,这样,即使连接断开,server中清除其引用,也不会造成作物或者内存泄漏。
但是再实际使用过程中,客户点断开会触发段错误,堆栈信息还是Qt事件循环中的,让人很纳闷。
<a class="wp-editor-md-post-content-link" href="/wp-content/uploads/2022/09/shapter.png" title="shapter.png"><img src="/wp-content/uploads/2022/09/shapter.png" alt="shapter.png" title="shapter.png" /></a>
很奇怪吧?很正常的引用计数智能指针的用法,结果却段崩溃了。
经过查找,确认原因:
再客户端Socket连接断开信号发出后,QTcpSocket 还在事件循环内残留未处理事件。当上层接收到信号,Server管理的引用技术清除,同时也把TcpSocket类给析构了。当处理完信号,再去处理事件,导致段错误。
解决方案: 重写 QSharedPointer的删除函数,使用QObject::deleteLater()
* Server
``` C++
static void doDeleteLater(SCPISocket *obj)
{
obj->deleteLater();
}
void RemoteSCPIServer::incomingConnection(qintptr handle)
{
auto socket = QSharedPointer(new SCPISocket(handle),doDeleteLater);//::create(handle);// new RemoteSocket(handle);
emit newConnect(socket);
}
```