Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ Qt Общие вопросы _ Exec threads etc...

Автор: me 17.2.2009, 23:03

Доброго времени суток..
Не так давно начал разбираться с взаимодействием потоков в qt и осознал что оч мощно запутался.
Есть задача: написать многопотоный ftp клиент который парсит файлик с заданиями и закачивает с разных ftp ресурсов файлы. Многопоточный просто для того чтобы самому в этом хорошенько разобраться. Решил организовать это следующим образом на каждое задание создаётся поток и далее вся работа через QFtp. Насколько я понял QFtp асинхронный, а так как мне требуется помимо непосредственно работы с ftp в этом же потоке обрабатывать некоторые действия со скачанными файлами то требуется вынести это в соотвествующие слоты. Не понятно мне стало вот что:
- насколько я понимаю слоты прикреплённые к соотвествующим сигналам из Qftp НЕ будет выполнятся до тех пор пока не будет вызван exec в потоке? Но тогда я так понима.ю мне придётся как минимум обращаться к потоку из главного потока дважды. Первый раз на старт потока а второй на запуск задания (просто слот с перечнем комманд на коннект и на скачку)
- получается что всю работу с ftp надо будет полностью выносить в отдельные слоты, что видится мне несколько грамоздким.

Вообщем требуется квалифицированная помощь)))
з.ы. Заранее хочу сказать, что листал книги по qt, читал документацию, но разобрался не во всём и эти моменты для меня туманны, так что просьба к великому гуглу не отсылать)))
з.з.ы Какой-нибудь простенький пример будет оч кстати.. ток главный смысл в том что программа будет автоматизированна и без участие человека и без гуи... так что явный инициатор QFtp c точки зрения запуска заданий отсуствует..
Заранее спасибо!

Автор: ViGOur 17.2.2009, 23:36

Работу с FTP не нужно выносить в отдельные слоты.

Давай попробуем разделить изучение того, что тебе нужно на шаги:
1. Для лучшего понимания почитай http://www.doc.crossplatform.ru/qt/4.3.2/signalsandslots.html () (если еще не читал)

2. Глянь пример работы с ftp: http://www.doc.crossplatform.ru/qt/4.3.2/network-ftp.html. А за одно и класс QFtp, для получения файла нужно использовать: http://www.doc.crossplatform.ru/qt/4.3.2/qftp.html#get. По данному примеру и сделай свою реализацию, пускай и консольную, GUI в примере чисто для визуализации.

3. Прочитай http://www.doc.crossplatform.ru/qt/4.3.2/threads.html.

А теперь, если ты на отлично понял 1, 2, 3 пункты, попробуй 'нарисовать' более ясную картину твоей реализации. Потому как у тебя явно полная каша в голове. :)

Автор: me 18.2.2009, 0:17

Цитата(ViGOur @ 17.2.2009, 23:36) *
А теперь, если ты на отлично понял 1, 2, 3 пункты, попробуй 'нарисовать' более ясную картину твоей реализации. Потому как у тебя явно полная каша в голове. :)


Вы не поверите.. про потоки и слоты я читал.. и пример тоже смотрел. Причём после предложения прочитать ещё раз я честно перечитал это ещё раз дабы освежить в памяти.

В голове у меня действительно сейчас небольшая каша..((( причём гланым образом из-за того что я не понимаю как конкретно работает асинхронный QFtp. Ведь как я понял функции аля connect и get сразу же возвращают значение при этом не блокируют поток... Вообще самое печальное что я не так давно делал и клиентское приложение и серверное приложение где было по потоку на соединение.. но там вроде всё получилось но видать не доразобрался до конца.

Попробую объяснить что я хотел бы сделать:

Главный поток парсит файл. На каждое задание он создаёт поток и передаёт конструктору потока адрес сервера, логин пароль, путь к папке на сервере, путь к локальной папке.
В потоке происходит подключение к ftp серверу. Считывается листинг директории (с вхождением в глубь) и проверяется по маске нужные нам файлы. После этого файлы скачиваются.

Неясный мне момент вот в чём:

void MyThread::run()
{
QFtp ftp = new QFtp;
ftp->connectToHost("ftp.trolltech.com");
ftp->login();
ftp->list();
......
ftp->close();
exec();
}

1) list будет испускать сигнал о каждой записи. Предположим что мы сделали коннект этого сигнала со слотом класса MyThread который их как-то обрабатывает. Скажите сигнал listInfo будет обрабатываться после exec или до??? или exec вообще здесь никак не влияет? Если бы речь шла о функциях которые отрабатывают сразу всё было бы ясно.. но здесь ведь асинхронная работа.. Или exec нам в данном случае вообще не нужен?

2) получается что если там где стоит .... я бы захотел как-то обрабатывать файлы которые скачал (будем считать что после list идёт скачка файлов) то тогда мне надо выносить это в слот, потому что скачивание файлов тоже асинхронно и мы можем перейти к выполнению этих операций тогда когда файлы ещё по существую не скачались.

3) добавлю к вопросу 1. Когда мы будем извещаться о том отработала например функция connectToHost (cоотвествующий сигнал что функция done)? в произвольный момент времени после её вызова или строго после exec?

з.ы. exec как много в этом звуке=((((

Автор: SABROG 18.2.2009, 0:37

Цитата(Гость_me_* @ 18.2.2009, 0:17) *
Скажите сигнал listInfo будет обрабатываться после exec или до???


Во время, скорее всего. Я так понял класс хоть и ассинхронный, но он не поточный, а событийный (event loop). Если ты застрянешь в каком-нибудь методе надолго и возвращение не будет в eventloop, то и слоты твои вызываться не будут.

Автор: me 18.2.2009, 1:01

Цитата(SABROG @ 18.2.2009, 0:37) *
Цитата(Гость_me_* @ 18.2.2009, 0:17) *
Скажите сигнал listInfo будет обрабатываться после exec или до???


Во время, скорее всего. Я так понял класс хоть и ассинхронный, но он не поточный, а событийный (event loop). Если ты застрянешь в каком-нибудь методе надолго и возвращение не будет в eventloop, то и слоты твои вызываться не будут.


Эмм... не понял. Что такое поточный класс?
"Если ты застрянешь в каком-нибудь методе надолго и возвращение не будет в eventloop, то и слоты твои вызываться не будут." - тоже не понял что вы имели ввиду(

Автор: SABROG 18.2.2009, 1:39

Цитата(Гость_me_* @ 18.2.2009, 1:01) *
Эмм... не понял. Что такое поточный класс?

Класс, который использует QThread, чтобы добиться асинхронности.

Цитата(Гость_me_* @ 18.2.2009, 1:01) *
тоже не понял что вы имели ввиду(

Что такое event loop читал в документации?

Автор: me 18.2.2009, 9:16

Цитата(SABROG @ 18.2.2009, 1:39) *
Что такое event loop читал в документации?


Вечером тормозил что-то... Тоесть судя по разсуждениям сигналы, испускаемый от QFtp смогут обрабатываться только после выполнения exec в потоке.. и без exec обрабатываться не будут..

Автор: SABROG 18.2.2009, 9:38

Это из-за того, что QAbstrackSocket не блокирующий. Не знаю как в linux'е, а в виндах асинхронность добивается путем принятия WM_сообщений окном от ОС. Естественно, если в этот момент выполняется какой-нибудь метод, то сама программа не получит сообщение о том, что пришли какие-то данные, пока не произойдет возврат в основной event loop, где Qt сможет забрать сообщения от ОС с помощью API.

Вообще, если сравнивать гуишный и консольный вариант программ Qt, то консольные программы тоже бывают разные. Тот вариант консольного приложения, что делает Qt не будет работать в DOS'e например. Это обычное Win32 console приложение, где главным окном является окно консоли и оно естественно поддерживает windows messages. В *nix'ах это уже другое приложение, оно будет работать без X-Server'а, там механизм событий уже иной.

Автор: me 18.2.2009, 9:51

Цитата(SABROG @ 18.2.2009, 9:38) *
Это из-за того, что QAbstrackSocket не блокирующий. Не знаю как в linux'е, а в виндах асинхронность добивается путем принятия WM_сообщений окном от ОС. Естественно, если в этот момент выполняется какой-нибудь метод, то сама программа не получит сообщение о том, что пришли какие-то данные, пока не произойдет возврат в основной event loop, где Qt сможет забрать сообщения от ОС с помощью API.


Вроде всё стало понятно, спасибо!
Эх.. а с блокирующими сокетами было бы всё несколько проще..

Автор: Константин 18.2.2009, 14:46

Гость_me_*,
для испускания сигналов петля событий не нужна. т.е. вообще. петля событий потоку необходима, если в этом потоке требуется асинхронно выполнять слоты.
твоя задача решается довольно просто, если разобраться со взаимодействием потоков в кутэ.
/* хотел привести ссылку на почитать, но не нашёл ( */

Автор: SABROG 18.2.2009, 14:58

Цитата(Гость_me_* @ 18.2.2009, 9:51) *
Эх.. а с блокирующими сокетами было бы всё несколько проще..


Для этого были созданы такие методы:

bool waitForConnected ( int msecs = 30000 )
bool waitForDisconnected ( int msecs = 30000 )
virtual bool waitForReadyRead ( int msecs = 30000 )

Т.е. все блокируется на 30 секунд, а там уже можешь либо повторить попытку, либо пользователю выдать еrror.

Автор: me 18.2.2009, 15:14

Цитата(Константин @ 18.2.2009, 14:46) *
Гость_me_*,
петля событий потоку необходима, если в этом потоке требуется асинхронно выполнять слоты.


Так у меня и получается что на асинхронный сигнал QFtp надо вызывать слот в этом же потоке.. (например как я уже говорил для анализа и работы листинга директории)

Автор: SABROG 18.2.2009, 15:45

Цитата(Гость_me_* @ 18.2.2009, 15:14) *
Так у меня и получается что на асинхронный сигнал QFtp надо вызывать слот в этом же потоке.. (например как я уже говорил для анализа и работы листинга директории)


Соединяй нужные сигналы от QFtp со слотами QThread и выполняй exec() в run(). Все, что будет одти до exec - выполняется один раз при запуске потока, потом exec() входит в "бесконечный" цикл, т.е. возврата из run() не будет до тех пор, пока не будет выход из eventloop, а это произойдет уже при завершении потока. Таким образом все слоты будут обрабатываться не до и не после exec, а во время.

QFtp::list() и прочие команды возвращают свой уникальный ID команды. Можно использовать этот номер, чтобы отслеживать, когда команда завершится. До завершения команды у тебя будут приходить сигналы listInfo(), можно его обрабатывать и заполнять свой список нужным образом, отсортировывая не нужное. В конце придет сигнал commandFinished(int id), IDшник сравниваешь с IDшником возвращенным от list()'a и если это он, то начинаем выкачивать файлы по одному из своего списка. Удаляя выкаченные файлы из списка. На завершение выкаченных файлов тоже будут приходить сигналы со своим id, там ты и будешь удалять из своего списка то, что скачалось. Когда список станет нулевым можно закрыть соединение QFtp::close(), удалить экземпляр QFtp::deleateLater() и завершить поток, т.е. сделать тот самый выход из exec() - quit()/exit(0).

Автор: Константин 18.2.2009, 16:35

по всей видимости, тут ещё имеют место проблемы с пониманием асинхронное работы сокетов кутэ.

Цитата
void MyThread::run()
{
QFtp ftp = new QFtp;
ftp->connectToHost("ftp.trolltech.com");
ftp->login();
ftp->list();
......
ftp->close();
exec();
}

так делать не нужно. тем более, что ftp будет создан в родительском потоке. вызывай exec(), а всю работу с фтп вынеси из run()

Автор: me 18.2.2009, 16:35

Цитата(SABROG @ 18.2.2009, 15:45) *
Таким образом все слоты будут обрабатываться не до и не после exec, а во время.


Тоесть начиная с того как был вызван непосредственно exec и мы вошли в event loop и до тех пока не вызовем завершить поток,
Цитата(SABROG @ 18.2.2009, 15:45) *
т.е. сделать тот самый выход из exec() - quit()/exit(0)


Большое спасибо!!! По этому поводу все вопросы в сущности и были)

Автор: me 18.2.2009, 16:42

Цитата(Константин @ 18.2.2009, 16:35) *
по всей видимости, тут ещё имеют место проблемы с пониманием асинхронное работы сокетов кутэ.


Так оно и есть...

Цитата(Константин @ 18.2.2009, 16:35) *
тем более, что ftp будет создан в родительском потоке


Я там торопился... не указатель а просто
QFtp ftp;

Тоесть сделать так:
void MyThread::run()
{
QFtp ftp;
ftp.connectToHost("ftp.trolltech.com");
exec();
}


а дальше уже ловить успешное подключение и действовать дальше ?

Автор: me 18.2.2009, 16:43

не забыв конечнео же сделать connectы всё какие нужно...

Автор: me 18.2.2009, 16:48

Цитата
Классы QHttp и QFtp предоставляют поддержку протоколов HTTP и FTP на клиентской стороне. Так как эти два протокола используются для решении похожих задач, классы QHttp и QFtp имеют много общих особенностей:

* Неблокирующее поведение. QHttp и QFtp асинхронны. Вы можете отправить ряд команд (также называемые "запросы" для HTTP). Команды выполнятся позже, когда управление вернется к циклу событий Qt.
* ID команд. Каждая команда имеет свой уникальный номер ID, который используется для слежения за выполнением команд. Например, QFtp передает сигналы commandStarted() и commandFinished() с ID команды для каждой выполняемой команды. У QHttp тоже имеются подобные сигналы requestStarted() и requestFinished().
* Индикаторы процесса передачи данных. QHttp и QFtp посылают сигналы при передаче данных (QFtp::dataTransferProgress(), QHttp::dataReadProgress() и QHttp::dataSendProgress()). Вы можете соединить данные сигналы, например, с QProgressBar::setProgress() или QProgressDialog::setProgress().
* Поддержка QIODevice. Оба класса поддерживают загрузку в и скачивание из QIODevice, дополнительно к API, основанному на QByteArray.

Есть два основных способа использования QHttp и QFtp. Самый обычный способ состоит в том, чтобы отслеживать ID команд и следить за выполнением каждой команды, соединившись с соответствующим сигналом. Другой способ состоит в том, чтобы запланировать все команды сразу и соединиться только с сигналом done(), который посылается, когда все команды были выполнены. Первый способ требует большего количества работы, но дает больший контроль над выполнением конкретных задач и позволяет вводить дополнительные команды, основанные на результате предыдущих. Что позволяет обеспечить пользователю детализированную обратную связь.

Чёрт, как-то забыл совсем что есть общая статья про сетевое взаиможействие и совсем упустил из виду что тамCC написанно.

Автор: Константин 18.2.2009, 16:49

Цитата(Гость_me_* @ 18.2.2009, 16:42) *
а дальше уже ловить успешное подключение и действовать дальше ?

не забыв конечнео же сделать connectы всё какие нужно...

именно. в таком случае при невозможности соединиться (connectToHost) можно выйти и завершить поток, даже не запуская его петлю событий (т.к. вряд ли тебе нужен поток с неподключённым фтп-клиентом)

Автор: me 18.2.2009, 17:41

Всем спасибо! Тему я думаю на этом закрыть можно..

Автор: Константин 18.2.2009, 18:02

Цитата(Гость_me_* @ 18.2.2009, 17:41) *
Всем спасибо!

жми соответствующую кнопочку :)

Автор: me 2.3.2009, 16:35

В продолжение этой темы.. как-то всё не доходили руки сесть за всё это, но как наконец-то сел начались новые непонимания..


Вначале код:
FtpThread.h:
#include "FtpThread.h"

void FtpThread::run()
{
    // определение и инициализация соединения
    ftpConnection = new QFtp();
    connect(ftpConnection, SIGNAL(commandFinished(int, bool)), this, SLOT(commandManage(int, bool)), Qt::DirectConnection);
    ftpConnection->connectToHost(this->server);
    qDebug() << "Thread" << QThread::currentThreadId();
    exec();
    }

FtpThread::FtpThread(QString *server, QString *login, QString *pass)
{
    if (server == NULL)
;                    //  пока без этого..
    else
        this->server = *server;
    if (login == NULL)
        this->login="anonymous";
    else
        this->login = *login;
    if (pass == NULL)
        this->pass = "password";
    else
        this->pass = *pass;
    ftpConnection = NULL;
}
void FtpThread::commandManage(int command, bool isError)
{
    if (isError == true)
    {
        qDebug() << "Error:" << command << ftpConnection->errorString();
        return;
    }
    else
    {
        switch(command)
        {
            case 1:        // connectToHost прошёл успешно.. логинимся
                qDebug() << "Loging 1" << QThread::currentThreadId();
                ftpConnection->login(this->login, this->pass);
                return;
            case 2:
                qDebug() << "Logged In 2" << QThread::currentThreadId();
                ftpConnection->close();
                return;
            case 3:
                qDebug() << "Closed 3" << QThread::currentThreadId();
                quit();
                return;
            default:
                qDebug() << "OPPA!!" << command << QThread::currentThreadId();
                return;
        }
    }

}

FtpThread.cpp
#ifndef FTPTHREAD_H_
#define FTPTHREAD_H_

#include <QtNetwork>
#include <QCoreApplication>
#include <QQueue>

// Тип загрузки. С сервера или на сервер.
enum LoadDirection {TO_THE_SERVER, FROM_THE_SERVER};

class FtpThread : public QThread
{
    Q_OBJECT

public:
    FtpThread(QString *server = NULL , QString *name = NULL, QString *pass = NULL);

public slots:
    void commandManage(int, bool);

protected:
    void run();

private:
    // настройки подключения к серверу
    QString server;
    QString login;
    QString pass;
    LoadDirection load;

    // (up/down)load settings
    QString from;
    QString to;

    // Ftp соединение.
    QFtp *ftpConnection;

    // Задания
    QQueue<QString> tasks;
};
#endif /* FTPTHREAD_H_ */

main.cpp
#include <QtCore>
#include <QtNetwork>
#include <QCoreApplication>
#include "FtpThread.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString host = "ftp.freebsd.org";
    QString user = "anonymous";
    QString pass = "pass";
    QString dir  = "/pub/FreeBSD/";

    qDebug() << "Main thread" << QThread::currentThreadId();

    FtpThread ftpWorker(&host);
    FtpThread ftpWorker2(&host);
    ftpWorker.start();
    ftpWorker2.start();
    //ftpWorker.wait();
//  qDebug() << QThread::currentThreadId() << "After Wait";
    return a.exec();
}


Вывод в консоль=(((

Main thread 3071719120 
Thread           3070524304 QFtp(0x80564b0)
Thread           3053738896 QFtp(0x805d8c0)
Loging 1        3070524304 QFtp(0x80564b0)
Logged In 2  3053738896 QFtp(0x805d8c0)
OPPA!! 4       3053738896 QFtp(0x805d8c0)
Closed 3       3070524304 QFtp(0x80564b0)


Толи на оба QFtp даётся общая последовательность ID команд.. толи не знаю. Если это так то очевидно лучше использовать
currentCommand () + слоты на запуск команды и конец команды...? Что я делаю не так??

Автор: SABROG 2.3.2009, 17:47

Рекомендация по поводу использования NULL: http://www.prog.org.ru/topic_7458_0.html

Автор: me 2.3.2009, 17:49

Забыл вырезать из вывода QFtp(0x80564b0) - эт я решил в какой=то момент посмотреть на адреса объектов QFtp для каждого потока...

Автор: me 2.3.2009, 18:20

Последовательность ID действительно одна для всех объектов QFtp ((
Прийдётся через currentThreadId и дальнейший анализ что за команда выполняется.. меня вот кстати всегда мучал вопрос а не может ли случиться так,
что если мы ловим сигнал от commandStarted и в слоте вызываем currentThreadId не может ли она хотя бы теоретически вернуть другую команду...

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)