如何在Qt中创建线程网络服务器? [英] How to make a threaded network server in Qt?

查看:283
本文介绍了如何在Qt中创建线程网络服务器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在一个线程的telnet服务器(每个连接一个线程),并不能弄清楚如何摆脱valgrind错误。



我在QThread的run()方法中创建了QTcpSocket:

  void TelnetConnection :: run()
{
tcpSocketPtr = new QTcpSocket
if(!tcpSocketPtr-> setSocketDescriptor(socketDescriptor)){
emit error(tcpSocketPtr-> error());
return;
}
....
}

想要断开与之连接的客户端:

  void TelnetConnection :: disconnectClient()
{
tcpSocketPtr- > disconnectFromHost();
}

并且插槽断开时调用的槽是:
void TelnetConnection :: clientDisconnected()

  {
tcpSocketPtr-> deleteLater
TelnetConnection :: s_clientCount--;
QThread :: quit(); //退出这个线程的事件循环
}

b $ b 1.删除clientDisconnected插槽中的QTcpSocket,但会导致读/写不稳定。 (偶然崩溃)
2.到clientDisconnected插槽中的deletelater,但会导致内存读/写错误
3.在线程的exec循环后删除但仍会导致内存读/写错误



根据我所读取的内容,deletelater如果在exec循环结束时调用,将在执行循环结束时运行。线程被删除。



我尝试创建的QTcpSocket与this作为父,但我的信号连接失败,因为父对VS不匹配错误。 (这将允许在线程销毁时删除QTcpSocket)。



正确的解决方法是什么?

解决方案

您的问题几乎完全来自重新实现 QThread 。不要这样做。将所有的功能放入 QObject ,然后使用将它移动到裸的 QThread moveToThread()。如果你只通过信号槽连接从外部访问你的对象,你就会在那里和那里。



首先,我总是引用一些实例 TelnetConnection telnetThread 。这只是为了使我明白我在谈论什么线程。



您目前为止显示的代码中的错误是:


  1. 您正在 run() emit错误(tcpSocketPtr-> error()) >方法。它是从 telnetThread 不同的线程调用 QObject run()



    telnetThread-> code>方法在 telnetThread 线程中执行。但是由moc生成的信号的实现被期望在你实例化的任何线程中被调用 QThread - 即 telnetThread-> thread() code>,并且该线程永远不能等于与 run()执行的线程。基本上,有点混乱,以下不变式成立:

      QThread * telnetThread ... 
    Q_ASSERT(telnetThread!= telnetThread - > thread());


  2. 您正在呼叫 tcpSocketPtr ,生活在 telnetThread ,从在另一个线程中执行的插槽。以下是:

      Q_ASSERT(tcpSocketPtr-> thread()== telnetThread);所有在 telnetThread 上声明的插槽


    $ b <正在不同的线程中执行 telnetThread 本身!因此, disconnectClient 的主体在GUI线程中执行,但它直接在 tcpSocketPtr 上调用方法。 / p>


以下是一种方法。它侦听端口8023. ^ D结束连接。接收大写字母 Q 后跟Enter / Return将会干净地关闭服务器。



strong>



注意:此示例已重构,最后一个服务器现已正确删除。



关心确保事情被干净地包装。注意,只需 quit()运行 QCoreApplication 就可以了,整理会自动进行。所以,所有的对象最终都被破坏和释放,没有什么应该崩溃。线程和服务器从它们的析构函数向控制台发送诊断消息。



代码支持Qt 4和Qt 5。



StoppingThread



QThread 添加了一个缺失的行为。通常,当销毁正在运行的线程时,您会收到一条警告消息和崩溃/未定义的行为。这个类,在销毁时,告诉线程的事件循环退出并等待线程实际完成。它使用就像 QThread 将是,除了它不会做傻的东西破坏。



ThreadedQObjectDeleter



当其线程被销毁时,删除给定的 QObject 。当线程在逻辑上拥有其对象时有用。这个逻辑所有权不是父子所有权,因为线程和逻辑上拥有的对象存在于不同的线程(!)。



构造函数是私有的,并提供了一个工厂方法。这是为了强制在自由存储(a.k.a.堆)上创建删除器。在堆栈上使用删除器可能是一个错误,因此此模式使用编译器来防止它发生。



对象不能移动到指定的线程,否则删除器的构造将受制于竞争条件 - 对象可能已经在线程内删除了自己。此前提条件已声明。



ServerFactory



生成新的服务器实例 newConnection 插槽被调用。该构造函数通过客户端 QObject QMetaObject 传递来创建。因此,这个类可以构造任何所需的 QObject ,而不需要使用模板。对它创建的对象只有一个要求:



它必须有一个 Q_INVOKABLE 构造函数, c $ c> QTcpSocket * 作为第一个参数, QObject * parent 作为第二个参数。它生成的对象创建时父对象设置为 nullptr



套接字的所有权传输到服务器。 / p>

ThreadedServerFactory



为所创建的每个服务器创建一个专用的StoppingThread,到这个线程。否则表现得像ServerFactory。



服务器的终止退出线程的事件循环,从而完成线程。已完成的线程被删除。



TelnetServer



实现一个简单的telnet服务器。接口由可调用的构造函数组成。构造函数获取要使用的套接字,并将套接字连接到内部插槽。函数非常简单,类只响应来自套接字的 readyRead 断开的信号。断开连接后,它会自行删除。



这不是真正的telnet服务器,因为telnet协议不是那么简单。因此,telnet客户端将使用这样的哑服务器。



main()



主函数创建一个服务器,一个服务器工厂,并将它们连接在一起。然后它告诉服务器侦听任何地址,端口8023上的连接,并启动主线程的事件循环。监听服务器和工厂住在主线程,但所有的服务器都在自己的线程,因为你可以很容易地看到看到欢迎消息。支持任意数量的服务器。

  #include< QCoreApplication> 
#include< QThread>
#include< QTcpServer>
#include< QTcpSocket>
#include< QAbstractEventDispatcher>
#include< QPointer>

#if QT_VERSION< QT_VERSION_CHECK(5,0,0)
#define Q_DECL_OVERRIDE override
#endif

//一个QThread在销毁时退出事件循环,
//等待为循环完成。
class StoppingThread:public QThread {
Q_OBJECT
public:
StoppingThread(QObject * parent = 0):QThread(parent){}
〜StoppingThread ();等待(); qDebug()<<这个; }
};

//在线程终止时删除线程中存在的对象。
class ThreadedQObjectDeleter:public QObject {
Q_OBJECT
QPointer< QObject> m_object;
ThreadedQObjectDeleter(QObject * object,QThread * thread):
QObject(thread),m_object(object){}
〜ThreadedQObjectDeleter(){
if(m_object& m_object-> thread()== 0){
delete m_object;
}
}
public:
static void addDeleter(QObject * object,QThread * thread){
//对象不能在线程中,否则我们有
//一个竞争条件。
Q_ASSERT(thread!= object-> thread());
new ThreadedQObjectDeleter(object,thread);
}
};

//当侦听服务器获得新连接时创建服务器
class ServerFactory:public QObject {
Q_OBJECT
QMetaObject m_server;
public:
ServerFactory(const QMetaObject& client,QObject * parent = 0):
QObject(parent),m_server(client){}
Q_SLOT void newConnection
QTcpServer * listeningServer = qobject_cast< QTcpServer *>(sender());
if(!listeningServer)return;
QTcpSocket * socket = listenServer-> nextPendingConnection();
if(!socket)return;
makeServerFor(socket);
}
protected:
virtual QObject * makeServerFor(QTcpSocket * socket){
QObject * server = m_server.newInstance(Q_ARG(QTcpSocket *,socket),Q_ARG 0));
socket-> setParent(server);
return server;
}
};

//一个服务器工厂,在各个线程中创建服务器。
//线程在完成后自动删除它自己。
//破坏线程也会删除服务器。
类ThreadedServerFactory:public ServerFactory {
Q_OBJECT
public:
ThreadedServerFactory(const QMetaObject& client,QObject * parent = 0):
ServerFactory {}
protected:
QObject * makeServerFor(QTcpSocket * socket)Q_DECL_OVERRIDE {
QObject * server = ServerFactory :: makeServerFor(socket);
QThread * thread = new StoppingThread(this);
connect(thread,SIGNAL(finished()),thread,SLOT(deleteLater());
connect(server,SIGNAL(destroyed()),thread,SLOT(quit()));
ThreadedQObjectDeleter :: addDeleter(server,thread);
server-> moveToThread(thread);
thread-> start();
return server;
}
};

//具有以下功能的te​​lnet服务器:
// 1.它回应它收到的一切,
// 2.它收到CR后显示一个笑脸,
// 3.它退出服务器^ C
// 4.收到Q时断开连接
类TelnetServer:public QObject {
Q_OBJECT
QTcpSocket * m_socket ;
bool m_firstInput;
Q_SLOT void readyRead(){
const QByteArray data = m_socket-> readAll();
if(m_firstInput){
QTextStream out(m_socket);
out<< 欢迎来自线程< thread()<< endl;
m_firstInput = false;
}
for(int i = 0; i char c = data [i];
if(c =='\004')/ * ^ D * / {m_socket-> close();打破; }
if(c =='Q'){QCoreApplication :: exit(0);打破; }
m_socket-> putChar(c);
if(c =='\r')m_socket-> write(\r\\\
:),4);
}
m_socket-> flush();
}
public:
Q_INVOKABLE TelnetServer(QTcpSocket * socket,QObject * parent = 0):
QObject(parent),m_socket(socket),m_firstInput b {
connect(m_socket,SIGNAL(readyRead()),SLOT(readyRead()));
connect(m_socket,SIGNAL(disconnected()),SLOT(deleteLater()));
}
〜TelnetServer(){qDebug()<这个; }
};

int main(int argc,char * argv [])
{
QCoreApplication a(argc,argv);
QTcpServer服务器;
ThreadedServerFactory factory(TelnetServer :: staticMetaObject);
factory.connect(& server,SIGNAL(newConnection()),SLOT(newConnection()));
server.listen(QHostAddress :: Any,8023);
return a.exec();
}

#includemain.moc


I'm working on a threaded telnet server (one thread per connection), and can't figure out how to get rid of valgrind errors. I've narrowed the problem down to WHERE I delete the tcpsocket.

I create the QTcpSocket in the run() method of the QThread:

void TelnetConnection::run()
{
    tcpSocketPtr = new QTcpSocket();  
    if (!tcpSocketPtr->setSocketDescriptor(socketDescriptor)) {
        emit error(tcpSocketPtr->error());
        return;
    }
    ....
}

When my app wants to disconnect the client I call:

void TelnetConnection::disconnectClient()
{
    tcpSocketPtr->disconnectFromHost();
}

and the slot which is called upon socket disconnect is: void TelnetConnection::clientDisconnected()

{
    tcpSocketPtr->deleteLater();  
    TelnetConnection::s_clientCount--;
    QThread::quit();  // Exit ths event loop for this thread
}

So, I've tried 1. to delete the QTcpSocket in the clientDisconnected slot but that causes read/write instability. (occasional crash) 2. to deletelater in the clientDisconnected slot but that causes memory read/write errors 3. to delete after the thread's exec loop but that still causes memory read/write errors 4. to deletelater after the thread's exec loop - and all errors are gone.

From what I read, deletelater if called AFTER the exec loop has terminated, will run when the thread is deleted. So while this works, I don't think this is the RIGHT way to to it.

I tried creating the QTcpSocket with "this" as the parent but then my signal connects failed because of parent vs this mismatch errors. (Which would allow the QTcpSocket to be deleted upon thread destruction).

What is the RIGHT way to fix this?

解决方案

Your problems stem almost entirely from reimplementing QThread. Don't do it. Put all of your functionality into a QObject, and then move it to a bare QThread using moveToThread(). If you only access your object from outside via signal slot connections, you'll be done right then and there.

First of all, I'll always refer to some instance of your TelnetConnection as telnetThread. This is just to make it obvious what thread am I talking about.

The errors in the code you've shown so far, are:

  1. You're calling emit error(tcpSocketPtr->error()) from within the run() method. It's called from telnetThread, a different thread than the QObject the signal lives in: it lives in telnetThread->thread().

    The run() method is executing within the telnetThread thread. But the signal's implementation, generated by moc, is expected to be called in whatever thread you instantiated QThread - namely telnetThread->thread(), and that thread can never be equal to the one where run() executes. Basically, somewhat confusingly, the following invariant holds:

    QThread * telnetThread ...
    Q_ASSERT(telnetThread != telnetThread->thread());
    

  2. You're calling methods on tcpSocketPtr, living in telnetThread, from slots that execute in another thread. The following holds:

    Q_ASSERT(tcpSocketPtr->thread() == telnetThread);
    

    All of the slots declared on your telnetThread are executing in a different thread from telnetThread itself! So, the body of disconnectClient executes in, say, the GUI thread, but it calls methods directly on tcpSocketPtr.

The following is one way of doing it. It listens on port 8023. ^D ends the connection. Receiving an uppercase Q followed by Enter/Return will cleanly shut down the server.

Introduction

Note: This example has been refactored, and the last server is now properly deleted.

Some care is paid to ensuring that things are wrapped up cleanly. Note that it's OK to simply quit() the running QCoreApplication, the wrap-up will happen automatically. So, all the objects are eventually destructed and freed, and nothing should be crashing. The thread and server emit diagnostic messages to the console from their destructor. This way it's apparent that things do get deleted.

The code supports both Qt 4 and Qt 5.

StoppingThread

Adds a missing behavior to QThread. Normally, when you destruct a running thread, you get a warning message and crash/undefined behavior. This class, upon destruction, tells the thread's event loop to quit and waits for the thread to actually finish. It's used just like QThread would be, except that it won't do silly things upon destruction.

ThreadedQObjectDeleter

Deletes a given QObject when its thread is destroyed. Useful when a thread logically owns its objects. This logical ownership is not a parent-child ownership, because the thread and the logically owned object live in different threads (!).

The constructor is private, and a factory method is provided. This is to enforce creation of the deleter on the free store (a.k.a. the heap). It'd be likely an error to make the deleter on the stack, so this pattern uses the compiler to prevent it from happening.

The object must not have been moved to the specified thread yet, otherwise the construction of the deleter would be subject to race conditions - the object could have already deleted itself within the thread. This precondition is asserted.

ServerFactory

Produces a new server instance when its newConnection slot is invoked. The constructor is passed a QMetaObject of the client QObject to create. Thus this class can construct "any" desired QObject without needing to use templates. There is only one requirement on the object that it creates:

It must have a Q_INVOKABLE constructor taking a QTcpSocket* as the first argument, and QObject *parent as the second argument. The objects it produces are created with parent set to nullptr.

The socket's ownership is transferred to the server.

ThreadedServerFactory

Creates a dedicated StoppingThread for each server made, and moves the server to this thread. Otherwise behaves like ServerFactory. The threads are owned by the factory and are properly disposed of when the factory is destructed.

A server's termination quits the thread's event loop and thus finishes the thread. The finished threads are deleted. Threads that are destructed prior to termination of a server will delete the now-dangling server.

TelnetServer

Implements a trivial telnet server. The interface consists of an invokable constructor. The constructor takes the socket to be used and connects the socket to the internal slots. The functionality is very simple, and the class only reacts to readyRead and disconnected signals from the socket. Upon disconnection, it deletes itself.

This is not really a telnet server, since the telnet protocol is not so trivial. It so happens that telnet clients will work with such dumb servers.

main()

The main function creates a server, a server factory, and connects them together. Then it tells the server to listen for connections on any address, port 8023, and starts the main thread's event loop. The listening server and the factory live in the main thread, but all the servers live in their own threads, as you can easily see when looking at the welcome message. An arbitrary number of servers is supported.

#include <QCoreApplication>
#include <QThread>
#include <QTcpServer>
#include <QTcpSocket>
#include <QAbstractEventDispatcher>
#include <QPointer>

#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
#define Q_DECL_OVERRIDE override
#endif

// A QThread that quits its event loop upon destruction,
// and waits for the loop to finish.
class StoppingThread : public QThread {
    Q_OBJECT
public:
    StoppingThread(QObject * parent = 0) : QThread(parent) {}
    ~StoppingThread() { quit(); wait(); qDebug() << this; }
};

// Deletes an object living in a thread upon thread's termination.
class ThreadedQObjectDeleter : public QObject {
    Q_OBJECT
    QPointer<QObject> m_object;
    ThreadedQObjectDeleter(QObject * object, QThread * thread) :
        QObject(thread), m_object(object) {}
    ~ThreadedQObjectDeleter() {
        if (m_object && m_object->thread() == 0) {
            delete m_object;
        }
    }
public:
    static void addDeleter(QObject * object, QThread * thread) {
        // The object must not be in the thread yet, otherwise we'd have
        // a race condition.
        Q_ASSERT(thread != object->thread());
        new ThreadedQObjectDeleter(object, thread);
    }
};

// Creates servers whenever the listening server gets a new connection
class ServerFactory : public QObject {
    Q_OBJECT
    QMetaObject m_server;
public:
    ServerFactory(const QMetaObject & client, QObject * parent = 0) :
        QObject(parent), m_server(client) {}
    Q_SLOT void newConnection() {
        QTcpServer * listeningServer = qobject_cast<QTcpServer*>(sender());
        if (!listeningServer) return;
        QTcpSocket * socket = listeningServer->nextPendingConnection();
        if (!socket) return;
        makeServerFor(socket);
    }
protected:
    virtual QObject * makeServerFor(QTcpSocket * socket) {
        QObject * server = m_server.newInstance(Q_ARG(QTcpSocket*, socket), Q_ARG(QObject*, 0));
        socket->setParent(server);
        return server;
    }
};

// A server factory that makes servers in individual threads.
// The threads automatically delete itselves upon finishing.
// Destructing the thread also deletes the server.
class ThreadedServerFactory : public ServerFactory {
    Q_OBJECT
public:
    ThreadedServerFactory(const QMetaObject & client, QObject * parent = 0) :
        ServerFactory(client, parent) {}
protected:
    QObject * makeServerFor(QTcpSocket * socket) Q_DECL_OVERRIDE {
        QObject * server = ServerFactory::makeServerFor(socket);
        QThread * thread = new StoppingThread(this);
        connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
        connect(server, SIGNAL(destroyed()), thread, SLOT(quit()));
        ThreadedQObjectDeleter::addDeleter(server, thread);
        server->moveToThread(thread);
        thread->start();
        return server;
    }
};

// A telnet server with following functionality:
// 1. It echoes everything it receives,
// 2. It shows a smiley face upon receiving CR,
// 3. It quits the server upon ^C
// 4. It disconnects upon receiving 'Q'
class TelnetServer : public QObject {
    Q_OBJECT
    QTcpSocket * m_socket;
    bool m_firstInput;
    Q_SLOT void readyRead() {
        const QByteArray data = m_socket->readAll();
        if (m_firstInput) {
            QTextStream out(m_socket);
            out << "Welcome from thread " << thread() << endl;
            m_firstInput = false;
        }
        for (int i = 0; i < data.length(); ++ i) {
            char c = data[i];
            if (c == '\004') /* ^D */ { m_socket->close(); break; }
            if (c == 'Q') { QCoreApplication::exit(0); break; }
            m_socket->putChar(c);
            if (c == '\r') m_socket->write("\r\n:)", 4);
        }
        m_socket->flush();
    }
public:
    Q_INVOKABLE TelnetServer(QTcpSocket * socket, QObject * parent = 0) :
        QObject(parent), m_socket(socket), m_firstInput(true)
    {
        connect(m_socket, SIGNAL(readyRead()), SLOT(readyRead()));
        connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater()));
    }
    ~TelnetServer() { qDebug() << this; }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QTcpServer server;
    ThreadedServerFactory factory(TelnetServer::staticMetaObject);
    factory.connect(&server, SIGNAL(newConnection()), SLOT(newConnection()));
    server.listen(QHostAddress::Any, 8023);
    return a.exec();
}

#include "main.moc"

这篇关于如何在Qt中创建线程网络服务器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆