记一个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<QByteArray(const QJsonArray & )> & 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);
}
</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);
}
```