记一个QSharedPointer 和 QTcpsocket一起使用的断开连接导致段错误问题

记一个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 QSharedPointer device() const {return _device;}
    void writeCMD(QSharedPointer & 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} { 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 & build) { /* auto this = sharedFromThis(); 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() { }
* 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);
}
本来这就是一个很简单的智能指针管理的程序,默认Server里保留其计数,如果有lambda再其他线程使用,再lamba的结构体中同时保留,这样,即使连接断开,server中清除其引用,也不会造成作物或者内存泄漏。 但是再实际使用过程中,客户点断开会触发段错误,堆栈信息还是Qt事件循环中的,让人很纳闷。 shapter.png 很奇怪吧?很正常的引用计数智能指针的用法,结果却段崩溃了。 经过查找,确认原因: 再客户端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); } “`