Russian Qt Forum

Qt => Работа с сетью => Тема начата: yesrus от Март 12, 2009, 16:48



Название: sockets + threads, помогите :(
Отправлено: yesrus от Март 12, 2009, 16:48
Здравствуйте!
Вообщем возникла проблема - подскажите пожалуйста как переделать пример blockingfortuneserver так, чтобы поток был не блокируемым (т.е. работал exec() евент луп в дочернем потоке).

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

итого:

1) как после(!) загрузки опций(в основном потоке) или клика кнопки в основном потоке вызвать дочерний(при этом передать ему параметры\опции которые были считанны в основном потоке) в котором создать сокет и установить коннект к серверу
2) как посылать сигнал из дочернего потока в основной (при получении данных (т.е. при получении данных в дочернем потоке слот вызывает по readyRead слот, слот обрабатывает данные и посылает результат с помощью emit в основной поток где на основе этих данных что либо меняется (кнопки и т.д.)))
3) как из основного потока посылать сигнал  на вызов слота в дочернем потоке (т.е. передать данные в слот в дочернем потоке (а слот после обработки передаст данные в сокет).(!!!) - это основная проблема т.к. тот же сокет я могу запустить в потоке, более того могу получить данные из него(в интернете полно примеров) т.к. могу связать его сигнал со слотом в основном потоке, НО я не могу вызвать слот дочернего потока (и сделать там write в сокет) из основного...пишет ерор...невозможно использовать сокет т.к. он в другом потоке (так и есть т.к. слот выполняется из основного потока т.к. его оттуда вызвали) так вот как вызвать слот таким образом, чтобы он отработал в дочернем потоке, а не в основном ? - этого нигде нет, потратил 2 дня на поиски и результата 0.

Единственная просьба - пишите пожалуйста как и что нужно изменить в примере blockingfortuneclient (не просто ссылки на функции..а весь синтаксис т.к. я новичек и многих нюансов не знаю...)
Заранее огромное спасибо за помощь!

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

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


Название: Re: sockets + threads, помогите :(
Отправлено: EhTemka от Март 12, 2009, 21:52
...
И основная проблема-как из главного потока (там где крутится интерфейс) послать сигнал(с данными) дочернему потоку(там где крутится сокет и установлен коннект с сервером).

А зачем сигнал?

Можно воспользовался мютексом.

Например myTread.h:
Код
C++ (Qt)
 
class MyThread : public QThread
{
 
public:
....
 
   void addUserData(UserData *userData);
 
protected:
   void run();
 
private:
 
QMutex mutex_;                          
QQueue<UserData *> listUserData_; // данные от основного потока    
 
};
 

myTread.cpp
Код
C++ (Qt)
 
....
 
void MyThread::addUserData(UserData *userData)
{
   QMutexLocker locker(&mutex_);  
   listUserData_.enqueue(userData);    
}
 
void MyThread::run()
{
   UserData *userData;
   for (;;) {
       mutex_.lock();
 
       userData= *listUserData_.begin();
       listUserData_.dequeue();
 
       mutex_.unlock();
 
      .....
 
      delete userData;
 
     ....
  }
}
 
 

и где-нибудь в основном потоке
Код
C++ (Qt)
 
 
...
 
thread->addUserData(new UserData(/*data*/));
 
...
 
 



Название: Re: sockets + threads, помогите :(
Отправлено: yesrus от Март 12, 2009, 22:47
Хотелось бы без циклов, именно в луп эвенте все обрабатывать т.к. тот же readyRead не будет работать в цикле (т.к. это сигнал и без эвент лупа он не работает). =(

п.с. как я понимаю цикл это еще и большая нагрузка на процессор ? =(


Название: Re: sockets + threads, помогите :(
Отправлено: BRE от Март 12, 2009, 23:07
Код
C++ (Qt)
class Thread : public QThread
{
public:
   Thread();
 
   void run();
 
public slots:
   void    sendData( const QString &str );
 
signals:
   void    dataReaded( const QString &str );
 
private:
   QTcpSocket    *m_socket;
};
 
Thread::Thread()
{
   ...
   m_socket = new QTcpSocket;
   ...
}
 
void Thread::run()
{
   m_socket->moveToThread( this );
   connect( m_socket, SIGNAL( readyRead() ), SLOT( readData() ) );
   ....
   exec();
}
 
void Thread::sendData(...)
{
   m_socket->write( .... );
}
 
void Thread::readData()
{
   m_socket->read( ... );
   emit dataReaded(....);
}
 
Создавай объект, связывай сигналы/слоты.
Или в чем проблема?


Название: Re: sockets + threads, помогите :(
Отправлено: yesrus от Март 12, 2009, 23:42
Я так и сделал первый раз...на что qt ругался...почти матом  :D (единственное отличие от Вашего кода-я сокет создавал сразу в run()

Сейчас написал:
Код:
class tcpclient : public QThread
{
    Q_OBJECT

public:
    tcpclient();
   ~tcpclient();

public slots:
    void sendData( const QString &str );


signals:
    void recvData( const QString &str );

private:
    QTcpSocket    *m_socket;
    QString hostName;
    quint16 port;

protected:
    void run();

};

tcpclient::tcpclient()
{
qDebug() << "Thread::constructor: " << QThread::currentThreadId();
m_socket = new QTcpSocket;
start();
}
tcpclient::~tcpclient()
{
}

void tcpclient::run()
{
  qDebug() << "Thread::run: " << QThread::currentThreadId();

  m_socket->moveToThread( this );
  connect( m_socket, SIGNAL( readyRead() ), SLOT( readData() ) );
  hostName = "test.ru";
  port = 5000;
  m_socket->connectToHost(hostName, port);
  exec();
}

void tcpclient::readData()
{
    qDebug() << "WorkerReadData: " << QThread::currentThreadId();
}

void tcpclient::sendData(const QString &str)
{

    qDebug() << "WorkerSendData: " << QThread::currentThreadId();
    m_socket->write("hi");
}
Далее в мейн треде создал объект:

tcpclient worker;
connect(this, SIGNAL(sendData(const QString &str )),worker, SLOT(sendData(const QString &str )));


И получил такой ответ в дебаге:

Цитировать
Thread::constructor:  0x28c
Thread::run:  0xf68
QObject::moveToThread: Current thread (0x3e4ac8) is not the object's thread (0x22fd00).
Cannot move to target thread (0x22fd00)

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x3ea188), parent's thread is QThread(0x3e4ac8), current thread is tcpclient(0x22fd00)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x3ea188), parent's thread is QThread(0x3e4ac8), current thread is tcpclient(0x22fd00)
ThreadMain:  0x28c
QThread: Destroyed while thread is still running
QObject: Cannot create children for a parent that is in a different thread.

Собственно именно про это я и говорил в 1-м посте =(
п.с. QThread::currentThreadId(); показывает, что и в каком треде отрабатывает.


Название: Re: sockets + threads, помогите :(
Отправлено: igor_bogomolov от Март 13, 2009, 00:09
Написал проектик. Исходнички прилогаются.
Компилируй.
Запускай Qt Demo -> Fortune Server, указывай выделенный порт.
Надеюсь поможет. 


Название: Re: sockets + threads, помогите :(
Отправлено: BRE от Март 13, 2009, 00:18
Я запарился, да.  :) m_socket->moveToThread( this ) переносим в конструктор.
После этого у меня работает, по крайней мере к fortuneserver подключается и ответ от него читает.


Название: Re: sockets + threads, помогите :(
Отправлено: igor_bogomolov от Март 13, 2009, 00:25
2 BRE Может легче m_socket = new QTcpSocket; в run-не делать, тогда moveToThread ненужен.
Или я чего-то недопонимаю?

И еще вопрос по moveToThread. У меня есть непониманиее этой функции.
Почему ей this передается, а не указатель на QThread? Run выполняется в другом потоке, отличном от того  в котором выполняется конструктор класса. Как ей передать указатель на поток в Run?


Название: Re: sockets + threads, помогите :(
Отправлено: yesrus от Март 13, 2009, 01:13
2BRE - как я и говорил ругается при вызове слота sendData
Цитировать
Thread::constructor:  0xdb4
Thread::run:  0xb3c
GuiThread:  0xdb4
WorkerReadData:  0xdb4
Далее нажимаю кнопку Send и в дебаге вижу
Цитировать
WorkerSendData:  0xdb4
QSocketNotifier: socket notifiers cannot be enabled from another thread

2igor_bogomolov - Спасибо за пример, но в нем нет как раз главной части - отправка сообщений из основного потока в сокет.

п.с. и почему то программа во время запуска только на долю секунды показывает досовское окошко и мгновенно завершает работу, в дебаге
Цитировать
exited with code 0


Название: Re: sockets + threads, помогите :(
Отправлено: igor_bogomolov от Март 13, 2009, 01:37
Подразумевалось общение с fortune server, оно все равно сообщения не обрботает.
Обновил архив - теперь с отправкой сообщения, бесполезной в данном случае, но все же



Название: Re: sockets + threads, помогите :(
Отправлено: yesrus от Март 13, 2009, 02:00
Вставил дебаг, результат ниже
Цитировать
WorkerConstructor:  0xdd4
Main:  0xdd4
WorkerRun:  0xd90
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x50a42a8), parent's thread is ThreadSocket(0x50a2300), current thread is QThread(0x3e4bc0)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x50a42a8), parent's thread is ThreadSocket(0x50a2300), current thread is QThread(0x3e4bc0)
WorkerConnectToHost:  0xdd4
WorkerConnected:  0xdd4
WorkerReadData:  0xdd4
QObject::connect: Cannot queue arguments of type 'QAbstractSocket::SocketError'
(Make sure 'QAbstractSocket::SocketError' is registered using qRegisterMetaType().)
WorkerSendData:  0xdd4

Как видно из лога сокет создается в основном треде (как результат подвешивает интерфейс на 5 сек при проверке Network operation timed out). =(


Название: Re: sockets + threads, помогите :(
Отправлено: igor_bogomolov от Март 13, 2009, 02:11
Попробуй добавить в конструктор Client
Код
C++ (Qt)
threadSocket->moveToThread(threadSocket);

Что пишет после добавления?


Название: Re: sockets + threads, помогите :(
Отправлено: igor_bogomolov от Март 13, 2009, 02:28
У меня
Цитировать
warning: main TID: 0xe14
warning: run TID: 0xa90


Название: Re: sockets + threads, помогите :(
Отправлено: yesrus от Март 13, 2009, 02:29
Изначально все, что в run() - выполняется в другом треде, это видно по WorkerRun:  0xd90, но как только идет попытка коннекта к серверу выскакивает QObject: Cannot create children for a parent that is in a different thread. потому, что сигнал на вызов слота приходит из мейнтреда, и как результат идет попытка выполнения слота в мейн треде -> ошибка т.к. сокет создан в другом треде(странно то, что в данном случае оно работает в основном...т.е сокет переносится в основной поток автоматически после ошибок...).


Название: Re: sockets + threads, помогите :(
Отправлено: igor_bogomolov от Март 13, 2009, 02:36
а moveToThread добавили?
У меня после добавления все правильно заработало.


Название: Re: sockets + threads, помогите :(
Отправлено: yesrus от Март 13, 2009, 02:50
Спасибо! Все заработало.
Добавил аналог в свою программу...все так же отлично заработало...
Все гениальное просто как говорится...

Остался 1 вопрос-как сделать коннект из потока в основной поток...т.е. к примеру я делаю в конструкторе GUI (worker это объект в котором выполняется сокет т.е. другой поток)
Цитировать
connect(this, SIGNAL(sendData( const QString & )), worker, SLOT(sendData(const QString &)));
Как сделать то же самое(так же соеденить сигнал sendData основного потока с слотом sendData дочернего потока) из потока ? т.е. в конструкторе объекта worker ?


Название: Re: sockets + threads, помогите :(
Отправлено: igor_bogomolov от Март 13, 2009, 03:10
Не совсем понимаю зачем?
Чем неустраивает коннект в GUI, Вы же все равно worker создаете в нем?


Название: Re: sockets + threads, помогите :(
Отправлено: yesrus от Март 13, 2009, 11:46
Ну к примеру будет 2 или больше нитей..и коннект между ними нужен.


Название: Re: sockets + threads, помогите :(
Отправлено: igor_bogomolov от Март 13, 2009, 11:53
Так в чем проблема?   
Код
C++ (Qt)
connect(MyThread1, SIGNAL(signalThread1()), MyThread2, SLOT(slotThread2()));
 


Название: Re: sockets + threads, помогите :(
Отправлено: yesrus от Март 13, 2009, 12:42
А к основному потоку ? Какой у него идентификатор.


Название: Re: sockets + threads, помогите :(
Отправлено: igor_bogomolov от Март 13, 2009, 12:50
this
Или я вопроса не понял? ???


Название: Re: sockets + threads, помогите :(
Отправлено: yesrus от Март 13, 2009, 14:03
Сделать коннект из к примеру run() функции дочернего потока, если написать this то будет соединение с этим потоком..а нужно с основным.


Название: Re: sockets + threads, помогите :(
Отправлено: igor_bogomolov от Март 13, 2009, 15:20
Если уж так сильно хочется, можно сделать так:

Код:
Client *pClient;
Client::Client(QWidget *parent) : QMainWindow(parent) {
        pClient = this;
        ...
}

Код:
#include "Client.h"
extern Client *pClient;

void ThreadSocket::run() {
    ...
    connect(this, SIGNAL(displayMessage(const QString &)), pClient, SLOT(displayMessage(const QString &)));
    ...
    exec();
}

Только на мой взгляд так делать неправельно. Еще раз повторюсь. Вы все равно создаете экземпляры класса ThreadSocket в конструкторе, в данном примере, класса Client. Вот и создавайте все коннекты между этими классами там же. Т.е

Код:
Client::Client(QWidget *parent) : QMainWindow(parent) {
    ...
    threadSocket = new ThreadSocket();
    connect(threadSocket, SIGNAL(displayMessage(const QString &)), this, SLOT(displayMessage(const QString &)));
    connect(this, SIGNAL(clientSignal()), threadSocket, SLOT(slotSocket()));
    ...
    threadSocket_1 = new ThreadSocket();
    connect(threadSocket_1, SIGNAL(displayMessage(const QString &)), this, SLOT(displayMessage(const QString &)));
    ...
    connect(threadSocket, SIGNAL(signalSocket()), threadSocket_1, SLOT(slotSocket_1));
    ...
}


Название: Re: sockets + threads, помогите :(
Отправлено: yesrus от Март 13, 2009, 15:43
Понятно, спасибо огромное за помощь!