[QT4] Многопоточный сервер

 
0
 
C++
ava
SwordOfDeath | 19.07.2009, 12:23
Всем привет

Столкнулся с проблемой... В примере Threaded Fortune Server не описан процесс обмена данными... они только посылаются и сразу же закрывается поток (что очень странно, смысла в многопоточности для такой проги ноль...)

Я пытался переделать и совместить Threaded Fortune Server c Loopback... И уже разобрался что в потоке нужно запустить event loop через exec() но натолкнулся на проблему.

Как я понял если для QTcpSocket указать socketDescriptor то он начинает отрабатывать сообщения этого сокета... Так вот если я в потоке пытаюсь поставить setSocketDescriptor(socketDescriptor) то ругается в окно вывода проги:

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x8c71a58), parent's thread is QThread(0x3e5030), current thread is MainThread(0x8c71a48)

А если переношу QTcpSocket в новосозданный поток (thread->tcpServerConnection.moveToThread(thread)), причем ругается в exec():

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QNativeSocketEngine(0x8c6e438), parent's thread is MainThread(0x8c794c0), current thread is QThread(0x3e5190)


В общем не понимаю я всей этой архитектуры... А Qt написали такие скудные примеры =\
Помогите кто чем сможет... На данный момент это простенький чат пишется для тестирования...

UPD:
Processing Qt 4.5 moveToThread() или метод “Мюнхгаузена”
Ответы (22)
ava
null56 | 19.07.2009, 22:56 #
попробую догадаться, но не совсем понял, где что создается
1) tcpServerConnection - это объект переменная член класса? создается где? в функции run() или нет?
2) если tcpServerConnection создается в функции run, то ты НЕ можешь делать вот так из основного потока

thread->tcpServerConnection->setSocketDescriptor(socketDescriptor)

Так как за объект класса QTcpSocket отвечает дочерний поток, ты должен ему передавать дескриптор сокета, средствами сигналов - слотов или посылая сообщения фунцией postEvent (sendEvent), где можешь передавать сам файловый дескриптор
3) По поводу этого я не уверен,

А если переношу QTcpSocket в новосозданный поток (thread->tcpServerConnection.moveToThread(thread)), причем ругается в exec():

а именно, где ты создаешь объект сокета... могу лишь предположить, что ты назначаешь родителя объекту tcpServerConnection, что делать, при использовании moveToThread НЕЛЬЗЯ
4) Хорошо бы привезти код, не весь, а только концептульный твоей проблеме, было бы нагляднее
ava
SABROG | 19.07.2009, 23:41 #
Я бы не рекомендовал использовать не свои классы с методом moveToThread. Этот метод за раз переносит только один объект, если при этом объект создает внутри себя или пытается использовать другие QObject'ы, то без их переноса возникнет проблема.
ava
SwordOfDeath | 20.07.2009, 11:01 #
Таксь последовал вашим замечаниям по поводу moveToThread, и теперь создаю tcpServerConnection в run()... Таким образом мне уже не нужно его куда либо переносить.

Покопавшись разобрался почему ругается в exec() созданного потока;
Я связываю сигнал из потока с слотом из главного потока:

connect(thread, SIGNAL(incMessage(int, QByteArray)), this, SLOT(parseMessage(int, QByteArray)));


В мануале написано что Сигнал-Слот без проблем работает между потоками... А тут вот грабли...
Или может это грабли с передаваемыми данными? Как правильно это делать?
ava
SABROG | 20.07.2009, 12:01 #
Проверь не пишется ли в консоль что-то связанное с QByteArray и qRegisterMetatype.
ava
SwordOfDeath | 20.07.2009, 12:12 #
Вот полный вывод начиная от старта и заканчивая закрытием:

Lowest section in C:\WINDOWS\system32\xpsp2res.dll is .rsrc at 00011000
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QNativeSocketEngine(0x3ed698), parent's thread is ChatHostThread(0x3ee8e8), current thread is QThread(0x3e5190)
QThread: Destroyed while thread is still running

ava
null56 | 20.07.2009, 12:52 #
Цитата (SwordOfDeath @ 20.7.2009,  12:12)
Вот полный вывод начиная от старта и заканчивая закрытием:

я имел в виду исходный код твоей программы
Цитата


QThread: Destroyed while thread is still running


это происходит потому, что программа закрывается до того, как завершатся дочерние потоки... подождать надо завершения
ava
SwordOfDeath | 20.07.2009, 17:57 #
Привожу частичный код

void ChatHostServer::incomingConnection(int socketDescriptor)
{
    ChatHostThread *thread = new ChatHostThread(socketDescriptor, this);
    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
    connect(thread, SIGNAL(incMessage(int, QByteArray)), this, SLOT(parseMessage(int, QByteArray)));
    thread->start();
}



void ChatHostThread::run(){
    tcpServerConnection = new QTcpSocket;
    if (!tcpServerConnection->setSocketDescriptor(socketDescriptor)) {
        emit error(tcpServerConnection->error());
        return;
    }
    connect(tcpServerConnection, SIGNAL(readyRead()), this, SLOT(incomingData()));
    exec();
}

Как видно он ничем не отличается от примеров Qt... кроме как запуском eventloop'a и связыванием сигнал-слота в разных потоках.


UPD:
Двигаюсь дальше... Ошибку вызывает не сам Сигнал-Слот и не его параметры... А код который описан в слоте... Так как будто этот код вызывается прямо в контексте потока который сгенерил сигнал... Указание Qt::QueuedConnection результата не дали...
ava
null56 | 20.07.2009, 19:29 #
Я набил пример тебе маленький, где использую ТВОЙ код...
Сервер висит в ожидании, при поступлении нового подключения создает объект нити, передавая туда дескриптор.
Клиент, коннектится к серверу, как только подлючается в слоте onConnected посылает строку серверу...
Все работает без проблем...
цепляй прикрепленный файл, там отдельно собирешь клиента и сервера, запустишь и посмотришь, правильно  ли я понял твою идею и твой вопрос
ava
null56 | 20.07.2009, 19:32 #
ЗЫ: естетсвенно, чтобы не было
Цитата


QThread: Destroyed while thread is still running


ты сам должен позаботиться о том, чтобы завершить eventloop и дочерний поток, перед тем, как завершится основной поток
ava
SwordOfDeath | 20.07.2009, 19:42 #
Спасибо огромное за помощь... Но кажется это не решит моей проблемы...
Так как вы в parseMessage(int value, QByteArray msg) вызываете qDebug() которому не важно в каком потоке он вызван...
Мне же в этой функции нужно работать с данными и вызывать функции главного потока...
ava
null56 | 20.07.2009, 19:55 #
Я не понимаю, приведи пример, того, чего ты хочешь

SLOT(parseMessage(int, QByteArray)));

слот будет обрабатываться в основном потоке, так как он создан вместе с оъектом ChatHostServer в основном потоке

Добавлено позднее:
Цитата


А код который описан в слоте... Так как будто этот код вызывается прямо в контексте потока который сгенерил сигнал... Указание Qt::QueuedConnection результата не дали...


приведи пример кода в слоте
ava
SwordOfDeath | 20.07.2009, 21:00 #
Продвинулся дальше...
При прихождении данных я передаю сообщение в основной поток... где его обрабатываю и отсылаю ответ назад в поток который прислал данные... и уже там слот:

void ChatHostThread::sendMessage(QByteArray message){
    tcpServerConnection->write(message);
}

Вот он то и создает ошибку... Чувствую себя паршиво что не отдебажил все как положено до самого конца =\

Также эту же ошибку вызывает добавление tcpServerConnection->write(tcpServerConnection->readAll()); в слот void ChatHostThread::incomingData()

Проверил на вашем примере... Та же ошибка при попыке отправки сообщения с сервера клиенту...
ava
null56 | 20.07.2009, 21:38 #
НИИИИИИИЗЗЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯ, написал же чуть выше про подобное...
Объект класса QTcpSocket обслуживается дочерней нитью и лезть к этому объекту из другой нити, нИИИИИИИИзЯ...
Можно передать эти данные дочерней ните, которая уже будет отсылать их твоему объекту сокету...
примерно так дополню твой класс ChatHostThread

// вот он сигнал, который будет передавать твое сообщение объекту в дочернем потоке
signals:
     void writeToSocket(QByteArray);

// функция сгенерит сигнал, который будет цепляться к слоту сокета, созданного в дочернем потоке
void ChatHostThread::sendMessage(QByteArray message)
{
    emit writeToSocket(message);
}


Функция run()

void ChatHostThread::run(){
    tcpServerConnection = new QTcpSocket;
    if (!tcpServerConnection->setSocketDescriptor(socketDescriptor)) {
        emit error(tcpServerConnection->error());
        return;
    }
    connect(tcpServerConnection, SIGNAL(readyRead()), this, SLOT(incomingData()));
    // ВОТ ТУТ ЦЕПАНИ СЛОТ
    connect(this, SIGNAL(writeToSocket(QByteArray)), tcpServerConnection, SLOT(onWrite(QByteArray)));
    exec();
}


Далее унаследуй класс от QTcpSocket и в функции run создавай уже его, чтобы определить этот недостающий слот... например

class TcpSocket : public QTcpSocket
{
.............
//а вот он слот, который ты цепанешь в функции run(), он будет работать, как и весь объект в дочернем потоке
public slots:
        void onWrite(QByteArray message)
        {
               // и уже вот тут ты точно будет в дочернем потоке
              write(message);
        }
.............
};


Переменную член класса так же делаешь своим TcpSocket и в функции run создаешь его же...

void ChatHostThread::run(){
    tcpServerConnection = new TcpSocket;


Теперь через такую Ж мы получили следующее:
вызов функции sendMessage(QByteArray message) (даже если это слот) сгенерит сигнал, слот которого обработается в дочернем потоке объекта нашего класса TcpSocket
во... )
ava
SwordOfDeath | 20.07.2009, 21:46 #
Так я так и делаю!...
Не лезу я к этому объекту из другой нити... Я все через Сигнал-Слоты гоняю...
И опять же проблему можно повторить и в вашем примере:
Добавив в ChatHostThread :: incomingData()

m_TcpServerConnection->write(msg);

Хм.. но данные отправляются...
ava
null56 | 20.07.2009, 21:48 #

void ChatHostThread::sendMessage(QByteArray message){
    tcpServerConnection->write(message);
}

это у тебя слот???
ava
SwordOfDeath | 20.07.2009, 22:03 #
chathostthread.h

#ifndef CHATHOSTTHREAD_H
#define CHATHOSTTHREAD_H

#include <QThread>
#include <QTcpSocket>
#include <QByteArray>

class ChatHostThread : public QThread
{
    Q_OBJECT

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

public slots:
    void incomingData();
    void sendMessage(QByteArray message);
signals:
    void error(QTcpSocket::SocketError socketError);
    void incMessage(int socketDescriptor, QByteArray message);

public:
    QString name;

    int socketDescriptor;
    QTcpSocket *tcpServerConnection;

    int totalBytes;
    int bytesReceived;
    QByteArray receivedData;
};

#endif // CHATHOSTTHREAD_H



chathostthread.cpp

#include "chathostthread.h"

ChatHostThread::ChatHostThread(int socketDescriptor, QObject *parent)
     : QThread(parent), socketDescriptor(socketDescriptor)
{
    socketDescriptor = 0;
    bytesReceived = 0;
    totalBytes =0;
    receivedData.clear();
}

void ChatHostThread::run(){
    tcpServerConnection = new QTcpSocket;
    if (!tcpServerConnection->setSocketDescriptor(socketDescriptor)) {
        emit error(tcpServerConnection->error());
        return;
    }
    connect(tcpServerConnection, SIGNAL(readyRead()), this, SLOT(incomingData()));
    exec();
}

void ChatHostThread::incomingData(){
    if (bytesReceived == 0){
        qint16 *ptr;
        bytesReceived = (int)tcpServerConnection->bytesAvailable();
        receivedData = tcpServerConnection->readAll();
        ptr =  (qint16*)receivedData.constData();
        totalBytes = *ptr;
    }else{
        bytesReceived += (int)tcpServerConnection->bytesAvailable();
        receivedData += tcpServerConnection->readAll();
    }

    if (bytesReceived == totalBytes){
        emit incMessage(socketDescriptor, receivedData.mid(2,receivedData.length()-2));
        bytesReceived = 0;
        totalBytes = 0;
        receivedData.clear();
    }
}
void ChatHostThread::sendMessage(QByteArray message){
    tcpServerConnection->write(message);
}



chathostserver.h

#ifndef CHATHOSTSERVER_H
#define CHATHOSTSERVER_H

#include <QTcpServer>
#include <QList>
#include <QString>
#include "chathostthread.h"

typedef struct{
    QString name;
}SUsr;

typedef struct{
    ChatHostThread *thread;
    int socketDescriptor;
    SUsr user;
}SCon;

class ChatHostServer : public QTcpServer
{
    Q_OBJECT

public:
    ChatHostServer();
    void send(int conn, qint16 action, QByteArray message);

public slots:
    void parseMessage(int socketDescriptor, QByteArray message);

signals:
    void sendMessage(QByteArray message);

protected:
    void incomingConnection(int socketDescriptor);

private:
    QList<SCon> connections;
};

#endif // CHATHOSTSERVER_H


chathostserver.cpp

#include "chathostserver.h"


ChatHostServer::ChatHostServer()
{
    SCon tmp;
    connections.clear();
    connections.append(tmp);
}

void ChatHostServer::incomingConnection(int socketDescriptor)
{
    SCon connection;
    ChatHostThread *thread = new ChatHostThread(socketDescriptor, this);
    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
    connect(thread, SIGNAL(incMessage(int, QByteArray)), this, SLOT(parseMessage(int, QByteArray)), Qt::QueuedConnection);

    connection.socketDescriptor = socketDescriptor;
    connection.thread = thread;
    connections.append(connection);
    thread->start();
}


void ChatHostServer::parseMessage(int socketDescriptor, QByteArray message){
    int i;
    for (i = 1; i < connections.size(); ++i){
        if (connections.at(i).socketDescriptor == socketDescriptor) break;
    }
    qint16 *ptr;
    ptr = (qint16*)message.constData(); // ID of function that user request

    switch (*ptr){
        case 1: // Function SetName
            connections[i].user.name = QString(message.constData()+2);
            send(i, 1, QByteArray("Logged in"));
            break;
        case 2: // Function BroadcastMessage
            if (!connections[i].user.name.isEmpty()){
                send(0, 2, message);
            }
            break;
    }
}

void ChatHostServer::send(int conn, qint16 action, QByteArray message){
    if (conn == 0){
        for (int i = 1; i < connections.size(); ++i){
            connect(this, SIGNAL(sendMessage(QByteArray)), connections[i].thread, SLOT(sendMessage(QByteArray)), Qt::QueuedConnection);
        }
    }else{
        connect(this, SIGNAL(sendMessage(QByteArray)), connections[conn].thread, SLOT(sendMessage(QByteArray)), Qt::QueuedConnection);
    }

    char *act = (char *) &action;
    message.prepend(QByteArray(act,sizeof(qint16)));
    qint16 tmp = message.size() + sizeof(qint16);
    char *size = (char *) &tmp;
    message.prepend(QByteArray(size,sizeof(qint16)));

    emit sendMessage(message);
    disconnect(SIGNAL(sendMessage(QByteArray)));
}

ava
null56 | 20.07.2009, 22:22 #
Значит смотри, в чем разница, что я написал и ты
У тебя есть класс со слотом

class ChatHostThread : public QThread
{
public slots:
    void sendMessage(QByteArray message);

Этот класс ты создаешь в основном потоке (если я ничего не пропустил), А ЗНАЧИТ твой слот sendMessage(QByteArray) после генерации сигнала sendMessage(QByteArray), будет так же обрабатываться в ОСНОВНОМ потоке

connect(this, SIGNAL(sendMessage(QByteArray)), connections[i].thread, SLOT(sendMessage(QByteArray)), Qt::QueuedConnection);

А что же это значит: что твой слот будет вызывать функцию write у объекта QTcpSocket в основном потоке... чего делать, похоже нельзя ) так как за объект отвечает дочерний поток

void ChatHostThread::sendMessage(QByteArray message){
    tcpServerConnection->write(message);
}


Я прикрепил модифицированный пример, где уже сервер шлет клиенту по таймеру сообщения, все работает и не жалуется
Каждые три секунды ты будешь получать от сервера сообщение
ava
SwordOfDeath | 20.07.2009, 22:31 #
Огромное спасибо... Наконец дошло...

Блин сделали красивую обвертку QThread а она вовсе некрасивая... Только запутывает...
Реально потерялся что чему пренадлежит...

Все понял... Исправлюсь!...
ava
null56 | 21.07.2009, 12:55 #
в твоем случае тебе бы помог "метод Мюнхгаузена", хорошо описанный SABROG
только надо было сделать это для объекта thread целиком

thread->moveToThread(thread);

тогда бы твой слот через сигнал обрабатывался в дочернем потоке и ошибок бы не было
ava
SwordOfDeath | 21.07.2009, 22:00 #
Когда юзал

thread->moveToThread(thread);

то ругалось так:

QObject::moveToThread: Cannot move objects with a parent


Убрал из конструктора указание parent и все пучком = )

Спаси огромное за реальную помощь... Мне нравится этот форум тем что здесь действительно помогают... Даже если вопросы глупые... как у меня например... Ведь мог бы и сам не поленится отдебажить и разобратся в чем проблема...
Просто проснулся как-то и решил попрограмить, настрочил настрочил без дебага... а потом думаю как бы его розгребсти то )
ava
ikss | 22.01.2011, 14:09 #
А такой не стандартный вопрос по теме, пробую поставить 85 порт, по нему ни как не получается завязатся, можно ли организовать связь через какой-то стандартный порт, вот открытые порты какие я вчера просканировал
21    открыт    ftp
22    открыт    ssh
25    открыт    smtp
53    открыт    domain
80    открыт    www
85    открыт    неизвестно
110    открыт    pop3
143    открыт    imap2
443    открыт    https

по QHttp нормально получается инфа через 85 порт, а когда ставлю его на connectToHost ни чего не происходит инфа не идет

------------------------------------------
Да не посмотрел на год темы =) но может кто подскажет всеж???
Зарегистрируйтесь или войдите, чтобы написать.
Фирма дня
Вы также можете добавить свою фирму в каталог IT-фирм, и публиковать статьи, новости, вакансии и другую информацию от имени фирмы.
Подробнее
Участники
advanced
Отправить