Russian Qt Forum

Qt => Работа с сетью => Тема начата: SubV от Сентябрь 05, 2007, 17:35



Название: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: SubV от Сентябрь 05, 2007, 17:35
Определены массивы соединений:
Код:
struct conn 
{
QTcpSocket* connection;
QScsConnectionHandler* handler;
};

QTcpServer scs_server;
QVector<conn> connArray;
conn connCurrent;

В главном цикле крутится опрос по заданному порту:
Код:
forever
{
QTcpServer scs_server;
scs_server.listen(QHostAddress::Any, 7001);
scs_server.waitForNewConnection(500, &timedOut);

while(scs_server.hasPendingConnections())
{
connCurrent.connection = scs_server.nextPendingConnection();
connCurrent.handler = new QScsConnectionHandler(connCurrent.connection);
connArray.append(connCurrent);
connCurrent.handler->start();
}

for (int i0=0;i0<connArray.size();i0++)
{
if(!connArray.at(i0).handler->new_connection)
{
delete connArray.at(i0).handler;
connArray.remove(i0);
}
}

if(checkForExit()) break;
}

Есть заготовка класса, который будет эти соединения обрабатывать:
Код:
class QScsConnectionHandler: public QThread
{
public:
QScsConnectionHandler(QTcpSocket* n_conn)
{
new_connection = n_conn;
}
~QScsConnectionHandler()
{
}
virtual void run();
QTcpSocket* new_connection;
protected:
private:
QString tmpStr, headerStr, currentID, currentIDn;
QByteArray b0a;

};

void QScsConnectionHandler::run()
{
forever
{
// process incoming connection
std::cout << "start\n";

tmpStr = "200 OK\r\n";
b0a.clear();
b0a.append(tmpStr);

new_connection->write(b0a);
new_connection->flush();
std::cout << "stop\n";

new_connection->close();
break;
}
new_connection = NULL;
}

При любой попытке вызова функций new_connection, т.е. write(), flush() или close программа либо падает, либо выводит сообщение вида "QObject: Cannot create children for a parent that is in a different thread. (Parent is QNativeSocketEngine(0086C628), parent's thread is QThread(00869B78), current thread is QThread(0086CAF8)".

В чём проблема и как решить? Архитектуру "сигнал-слот" в данном случае использовать нежелательно по ряду причин.


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: WW от Сентябрь 05, 2007, 18:52
Попробуй QObject::moveToThread().
Хотя может и не сработать.


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: denka от Сентябрь 05, 2007, 21:07
А почему ты не воспользуешся ф-цией incomingConnection ( int socketDescriptor )  класса QTcpServer, как это сделанно в примере hreadedfortuneserver? Создавал бы сам QTcpSocket в run на основе полученого дескриптора и не было бы проблемы с парентом в другом потоке...


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: SubV от Сентябрь 06, 2007, 15:51
Попробовал использовать incomingConnection и дескрипторы.
Код:
class SmSrv : public QTcpServer
{
Q_OBJECT

public:
SmSrv(QObject *parent = 0);
~SmSrv();

QVector<SmConnHandler*> cns;
void checkForInactiveConnections();

private:
protected:
void incomingConnection(int handle);
};
Код:
#include "smsrv.h"

SmSrv::SmSrv(QObject *parent)
: QTcpServer(parent)
{
listen(QHostAddress::Any, 7001);
}

SmSrv::~SmSrv()
{
}

void SmSrv::checkForInactiveConnections()
{
for (int i0=0;i0<cns.size();i0++)
if(cns.at(i0)->isFinished())
{
delete cns.at(i0);
cns.remove(i0);
}
}

void SmSrv::incomingConnection(int handle)
{
SmConnHandler* cn = new SmConnHandler(handle, this);
cns.append(cn);
cn->start();
}

Теперь проблема в том, что вызов переопределенной incomingConnection() вообще не происходит. У кого-нибудь есть мысли по этому поводу?


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: BD от Сентябрь 07, 2007, 11:33
Не совсем понял зачем тебе нужна структура
Код:
struct conn 
{
Когда у тебя в handler также есть ссылка на connection.
Кроме того, я так пониманию, после обработки соединения, у тебя будет снова создаваться сервер на том же порту, потому что я не вижу где ты его удаляешь // Я про первый вариант
В общем, попробуй что-то типа
Код:
	QTcpServer scs_server;
QVector<QScsConnectionHandler *> connArray;

Код:
QTcpServer scs_server;
scs_server.listen(QHostAddress::Any, 7001);
forever
    scs_server.waitForNewConnection(500, &timedOut);
    while(scs_server.hasPendingConnections())
{
                     conHandler * connCurrent  = new QScsConnectionHandler( scs_server.nextPendingConnection() );
                     connHandler -> start();
                     connArray.append( conHandler);                     
}
if(checkForExit()) break;
}

Код:
class QScsConnectionHandler: public QThread
{
public:
QScsConnectionHandler(QTcpSocket* n_conn)
{
new_connection = n_conn;
}
~QScsConnectionHandler()
{
}
virtual void run();
QTcpSocket* new_connection;
protected:
private:
QString tmpStr, headerStr, currentID, currentIDn;
QByteArray b0a;

};

void QScsConnectionHandler::run()
{
       while ( new_connection -> state!= QAbstractSocket::UnconnectedState ) {
// process incoming connection
                только тогда здесь перед close желательно использовать сначала disconnect
}
        deleteLater ();
}


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: SubV от Сентябрь 07, 2007, 16:32
Вроде бы разобрался... причина, судя по всему - странная нелюбовь QT к созданию объектов в потоке по указателю. Привожу рабочий скелет кода.
main.cpp
Код:
#include <QtCore/QCoreApplication>
#include <QDatetime>
#include <QStringList>
#include <QVariant>
#include <iostream>
#include "qscsmisc.h"

#include "smsrv.h"

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
SmSrv srv;
while(srv.isListening())
{
a.processEvents();
srv.checkForInactiveConnections();
if(checkForExit()) break;
}

return 0; /*a.exec();*/
}

smsrv.h
Код:
#ifndef SMSRV_H
#define SMSRV_H

#include <QObject>
#include <QtNetwork>
#include <QtSql>
#include <QThread>
#include <QTcpSocket>
#include "smconnhandler.h"

class SmSrv : public QTcpServer
{
Q_OBJECT

public:
SmSrv(QObject *parent = 0);
~SmSrv();

QVector<SmConnHandler*> cns;
void checkForInactiveConnections();

private:
protected:
void incomingConnection(int handle);
};

#endif // SMSRV_H

smsrv.cpp
Код:
#include "smsrv.h"

SmSrv::SmSrv(QObject *parent)
: QTcpServer(parent)
{
listen(QHostAddress::Any, 7001);
}

SmSrv::~SmSrv()
{
}

void SmSrv::checkForInactiveConnections()
{
for (int i0=0;i0<cns.size();i0++)
if(cns.at(i0)->isFinished())
{
delete cns.at(i0);
cns.remove(i0);
}
}

void SmSrv::incomingConnection(int handle)
{
std::cout << "+1\n";
SmConnHandler* cn = new SmConnHandler(handle, this);
cns.append(cn);
cn->start();
}

smсonnhandler.h
Код:
#ifndef SMCONNHANDLER_H
#define SMCONNHANDLER_H

#include <QThread>
#include <QTcpSocket>
#include <iostream>
#include <stdlib.h>

class SmConnHandler : public QThread
{
Q_OBJECT

public:
SmConnHandler(int socketDescriptor, QObject *parent);
~SmConnHandler();
void run();

QTcpSocket* new_connection;
int sDescr;
private:

};

#endif // SMCONNHANDLER_H

smсonnhandler.cpp
Код:
#include "smconnhandler.h"

SmConnHandler::SmConnHandler(int socketDescriptor, QObject *parent)
: QThread(parent)
{
sDescr = socketDescriptor;
}

SmConnHandler::~SmConnHandler()
{
}

void SmConnHandler::run()
{
QByteArray b0a;
QString tmpStr, headerStr, currentID, currentIDn;
//new_connection = new QTcpSocket; -- хотя и работает, но выдает ошибку, см. ниже.
QTcpSocket new_connection;
new_connection.setSocketDescriptor(sDescr);
forever
{
std::cout << "start\n";
tmpStr = "200 OK\r\n";

b0a.clear();
b0a.append(tmpStr);

//new_connection->write(b0a);
                //если обратиться к write() по указателю, будет ошибка
                //QObject: Cannot create children for a parent that is in a different thread. (Parent is
                //QNativeSocketEngine(0086C4A0), parent's thread is QThread(00865828), current thread is
                //SmConnHandler(0086B990)

new_connection.write(b0a);
new_connection.flush();

std::cout << "stop\n";

new_connection.close();
break;
}
}


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: denka от Сентябрь 07, 2007, 17:23
Проблема не в том что Qt не любит указателию Скорей всего ты создавал новый объект так:
Код:
QTcpSocket * new_connection = new QTcpSocket(this);
А так нельзя так как экземпляр SmConnHandler создавался в другом потоке... у тебя в начале была похожая ошибка...
Относительно твоего первого примера:
Цитировать
QObject is reentrant. Most of its non-GUI subclasses, such as QTimer, QTcpSocket, QUdpSocket, QHttp, QFtp, and QProcess, are also reentrant, making it possible to use these classes from multiple threads simultaneously. Note that these classes are designed to be created and used from within a single thread; creating an object in one thread and calling its functions from another thread is not guaranteed to work.
Относительно последнего:
Цитировать
The child of a QObject must always be created in the thread where the parent was created. This implies, among other things, that you should never pass the QThread object (this) as the parent of an object created in the thread (since the QThread object itself was created in another thread).
Т.е. зделаеш так:
Код:
QTcpSocket new_connection(this);
получиш те же яйца тока без указателя... Так что указатели тут не причем :)


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: SubV от Сентябрь 07, 2007, 18:24
den'ka, я в курсе насчёт многопоточности наследников QObject. Всё дело в том, что работают оба варианта - как с созданием QTcpSocket через указатель, так и напрямую. Фишка в том, что второй вариант не ругается при выполнении.

Предупреждение "Cannot create children for a parent that is in a different thread. (Parent is QNativeSocketEngine..." выводится в консоль, поэтому вероятно, многие, кто пишет GUI просто его не наблюдали.


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: denka от Сентябрь 08, 2007, 08:46
Да?  :) Или я тебя не правильно понял или ты говориш что есть разница между тем как создаеться объект в стеке или в куче... Тогда объясни вот что... Код:
Код:
QTcpSocket new_connection(this);
Вызывает это самое сообщение о ктором ты говориш, а код:
Код:
QTcpSocket * new_connection = new QTcpSocket;
Не вызывает... Еще раз повторюсь дело не в том как создаеться объект а в том что ты указываеш парента который создан в другом потоке.


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: SubV от Сентябрь 10, 2007, 16:16
...или ты говориш что есть разница между тем как создаеться объект в стеке или в куче... Тогда объясни вот что... Код:
Код:
QTcpSocket new_connection; // без (this);
Вызывает это самое сообщение о ктором ты говориш, а код:
Код:
QTcpSocket * new_connection = new QTcpSocket;
Не вызывает... Еще раз повторюсь дело не в том как создаеться объект а в том что ты указываеш парента который создан в другом потоке.

Как раз первый вариант НЕ ругается. А второй, т.е. при выделении памяти в хипе, отрабатывает нормально, НО выводит в консоль сообщение об ошибке, приведенное выше. Никакого парента, созданного в другом потоке, я не указываю. Очевидно, это одна из "фич" QT, с которой нужно просто смириться.

Еще раз скажу, что оба варианта имеют одинаковую функциональность, т.е. сокет содается, приём/передача данных работают нормально.


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: denka от Сентябрь 10, 2007, 16:46
Тогда объясни мне почему у меня не пишет этого сообщения? Какая может быть фича? По твоему троли каким-то образом проверяют как создаеться объект? Невериш мне так хотя бы поверь своим глазам и размышляй логически. Тебе в консоль что выводит? Что дочерный объект в разных потоках с парентом... Это как могло произойти? Только когда создаеш объект и указываеш парента из другого потока или когда сам явно устанавливаеш


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: SubV от Сентябрь 10, 2007, 19:12
Вот, у человека была та же проблема. См. его отладочный лог.

http://www.qtcentre.org/forum/f-qt-programming-2/t-threaded-sever-problem-4384.html

Внятного объяснения так и не нашлось.


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: pastor от Сентябрь 10, 2007, 22:27
Решил отписаться, но забераю свои слова назад. Добавлю лишь одно: способ размещения объекта (стек или хип) точно не причём. Сделал тестовый примерчик (в точности как у тебя), никаких сообщений не получил в консоль. Все работает как надо. имхо, проблема кроеться в другом месте. В тесте выполнял подключение к серверу 100 раз в цикле. Qt 4.2.3/4.3.0 VS2005+SP1. Сегодня качну 4.3.1 проверю на ней, но думаю результат останется прежним.

Попробуйте пройтись дебагером в самой Qt и посмотреть что к чему.


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: SubV от Сентябрь 11, 2007, 14:50
У меня QT 4.2.3/4.3.0/4.3.1, VS2003 + SP1.

Неужели дело в компиляторе? Впрочем, создание сокета в стеке на данный момент меня устраивает. Так что проехали..


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: denka от Сентябрь 11, 2007, 15:22
Я тут покапался в исходниках Qt. Вот тебе последовательность вызовов конструкторов для QTcpSocket с их исходниками...

Код:
QTcpSocket::QTcpSocket(QObject *parent)
    : QAbstractSocket(TcpSocket, *new QTcpSocketPrivate, parent)
{
#if defined(QTCPSOCKET_DEBUG)
    qDebug("QTcpSocket::QTcpSocket()");
#endif
    d_func()->isBuffered = true;
}

QAbstractSocket::QAbstractSocket(SocketType socketType,
                                 QAbstractSocketPrivate &dd, QObject *parent)
    : QIODevice(dd, parent)
{
    Q_D(QAbstractSocket);
#if defined(QABSTRACTSOCKET_DEBUG)
    qDebug("QAbstractSocket::QAbstractSocket(%sSocket, QAbstractSocketPrivate == %p, parent == %p)",
           socketType == TcpSocket ? "Tcp" : socketType == UdpSocket
           ? "Udp" : "Unknown", &dd, parent);
#endif
    d->socketType = socketType;
}

QIODevice::QIODevice(QIODevicePrivate &dd, QObject *parent)
    : QObject(dd, parent)
{
}

QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
    Q_D(QObject);
    ::qt_addObject(d_ptr->q_ptr = this);
    d->threadData = QThreadData::current();
    d->threadData->ref();
    if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
        parent = 0;
    if (d->isWidget) {
        if (parent) {
            d->parent = parent;
            d->parent->d_func()->children.append(this);
        }
        // no events sent here, this is done at the end of the QWidget constructor
    } else {
        setParent(parent);
    }
}

static bool check_parent_thread(QObject *parent,
                                QThreadData *parentThreadData,
                                QThreadData *currentThreadData)
{
    if (parent && parentThreadData != currentThreadData) {
        QThread *parentThread = parentThreadData->thread;
        QThread *currentThread = currentThreadData->thread;
        qWarning("QObject: Cannot create children for a parent that is in a different thread.\n"
                 "(Parent is %s(%p), parent's thread is %s(%p), current thread is %s(%p)",
                 parent->metaObject()->className(),
                 parent,
                 parentThread ? parentThread->metaObject()->className() : "QThread",
                 parentThread,
                 currentThread ? currentThread->metaObject()->className() : "QThread",
                 currentThread);
        return false;
    }
    return true;
}

как видиш меседж идет из ф-ции check_parent_thread  и только в том случае если parent не равен 0 он выводиться :)

З.Ы. Думаю в твоем случае это не компилятор, скорей погода или магнитная буря... Тут нужен хороший танец с бубнами ;)


Название: Re: [4.3.1] Консольный обработчик входящих соединений - в чём проблема?
Отправлено: pastor от Сентябрь 11, 2007, 17:15
Проверил на Qt 4.3.1. Всё работает нормально.

BWT: Удалось воспроизвести вывод в консоль лишь в том случае, когда был указан парент:

Цитировать
QObject: Cannot create children for a parent that is in a different thread.
(Parent is SmConnHandler(01AAA1F8), parent's thread is QThread(003A2CC0), current thread is SmConnHandler(01AAA1F8)

имхо, это то про что говорил den'ka в своих постах, в том числе и последнем.