Russian Qt Forum

Qt => Общие вопросы => Тема начата: niklep от Апрель 11, 2011, 08:58



Название: Параметры сигналов/слотов
Отправлено: niklep от Апрель 11, 2011, 08:58
Доброго времени суток. Недавно начал смотреть в сторону Qt для диплома. Пишу приложения (клиент/сервер), которые через сокет обмениваются данными. В данный момент работаю над усовершенствованием клиента. Для того, чтобы клиент при незапущенном сервере пытался устанавливать соединение, и при появлении в сети сервера подключался, создал таймер. При появлении сигнала таймера timeout() вызывался слот slotConnectToHost(), в котором происходило примерно следующее:
Код:
tcpSocket->connectToHost(host,port);
Но GUI при этом не отвечают в момент, когда по таймеру выполняются события. Решил часть обработки вынести в поток. Здесь возникла проблема с параметрами сигналов/слотов (не могу привести к такому виду, чтобы они совпадали). Буду благодарен, если кто-нибудь подкинет какую-нибудь идейку или хитрый прием.
Описываю ситуацию:
1. Есть класс Client.
Код:
class Client : public QWidget
{
Q_OBJECT
private:
QLineEdit* txtInput;
quint16 nextBlockSize;
QTextEdit* txtInfo;
MyThread* timerThread;

public:
Client(const QString& strHost, int nPort, QWidget *parent = 0, Qt::WFlags flags = 0);
QString host;
int port;
QTcpSocket* tcpSocket;

private slots:
void slotReadyRead ();
void slotError (QAbstractSocket::SocketError);
void slotSendToServer();
void slotConnected();
};


2. Есть класс MyThread.
Код:
class MyThread : public QThread
{
Q_OBJECT

public:
MyThread(QObject *parent);
void run(Client* client);
void stop();
bool getStopped();
void setStoppedToFalse();

private:
QTimer* tcpTimer;
volatile bool stopped;
volatile bool timerExist;

private slots:
void slotConnectToHost(Client *some_client);
};

Слот slotConnectToHost в классе MyThread принимает параметром объект класса Client (так как сокет принадлежит именно объекту client):
Код:
void MyThread::slotConnectToHost(Client *some_client)
{
some_client->tcpSocket->connectToHost(some_client->host,some_client->port);
if (some_client->tcpSocket->waitForConnected())
this->stop();
}

Поэтому, логично, при соединении объектов (через connect) необходимо указывать слот slotConnectToHost именно с параметром:
Код:
void MyThread::run(Client* client)
{
if (!stopped)
{
qDebug() << "Thread RUN";
if (!timerExist)
{
tcpTimer = new QTimer(this);
connect(tcpTimer, SIGNAL(timeout()), SLOT(slotConnectToHost(client)));
timerExist = true;
}
tcpTimer->start(5000);
}
}

Вот здесь и ошибка. Сигнал timeout не имеет параметров. Вроде внятно объяснил суть проблемы. Есть идеи?


Название: Re: Параметры сигналов/слотов
Отправлено: Mikhail от Апрель 11, 2011, 09:31
Client *some_client передай потоку в конструкторе или иной инициализирующей функции.
А слот оставь без параметров.


Название: Re: Параметры сигналов/слотов
Отправлено: mutineer от Апрель 11, 2011, 09:45
При соединении сигнала и слота ты не передаешь параметры, которые будут при вызове слота, а указываешь их типы. Параметры в слот передает сигнал при испускании. Почитай внимательнее про сигналы и слоты


Название: Re: Параметры сигналов/слотов
Отправлено: niklep от Апрель 11, 2011, 21:49
Спасибо за совет, просто я далеко не ас в программировании, тем более ООП=) Решил проблему, все скомпилировалось и работает, но вот только такое ощущение, что программе наплевать на то, что я создал поток.
Когда моя программа была однопоточной, GUI не отвечало именно в тот момент, когда срабатывал сигнал таймера timeout(). Вот я и решил все действия, выполняемые по таймеру, вынести в поток. Но сейчас понимаю, что где-то я прогадал...
Класс клиента: client.cpp
Код:
Client::Client(const QString& strHost, int nPort, QWidget *parent, Qt::WFlags flags)
: QWidget(parent, flags), nextBlockSize(0)
{
        // создаем GUI
txtInfo = new QTextEdit;
txtInput = new QLineEdit;
txtInfo->setReadOnly(true);
QPushButton* pcmd = new QPushButton("Send");
QVBoxLayout* pvbxLayout = new QVBoxLayout;
pvbxLayout->addWidget(new QLabel("<H1>Client</H1>"));
pvbxLayout->addWidget(txtInfo);
pvbxLayout->addWidget(txtInput);
pvbxLayout->addWidget(pcmd);
setLayout(pvbxLayout);

host = strHost;
port = nPort;

        // создаем сокет и создаем поток, который отвечает за коннект сокета к серверу
tcpSocket = new QTcpSocket(this);
timerThread = new MyThread(this, this);
timerThread->run();

connect(tcpSocket, SIGNAL(connected()),
             SLOT(slotConnected()));
connect(tcpSocket, SIGNAL(readyRead()),
             SLOT(slotReadyRead()));
connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
             this, SLOT(slotError(QAbstractSocket::SocketError)));
connect(pcmd, SIGNAL(clicked()),
             SLOT(slotSendToServer()));
connect(txtInput, SIGNAL(returnPressed()),
this, SLOT(slotSendToServer()));
}

void Client::slotReadyRead()
{
// неважно
}

void Client::slotError(QAbstractSocket::SocketError err)
{
QTime time;
QSqlQuery query;
query.prepare("INSERT INTO client (Time, Event, Code) VALUES (:time, 'Error', :code);");
query.bindValue(":time",time.currentTime().toString());

QString strError = time.currentTime().toString() + " Error: ";
if (err==QAbstractSocket::HostNotFoundError)
{
strError = strError + "The host was not found";
query.bindValue(":code","The host was not found");
}
else if (err==QAbstractSocket::RemoteHostClosedError)
{
strError = strError + "The remote host is closed";
query.bindValue(":code","The remote host is closed");
}
else if (err==QAbstractSocket::ConnectionRefusedError)
{
strError = strError + "The Connection was refused";
query.bindValue(":code","The Connection was refused");
}
else
{
strError = strError + tcpSocket->errorString();
query.bindValue(":code",tcpSocket->errorString());
}
query.exec();
txtInfo->append(strError);

        // при возникновении ошибки (например когда сервер не запущен), если поток в данный момент не занят подключением (такая ситуация возникает если сервер был открыт и вдруг закрылся), вновь создать поток, который начнет искать подключение
if (timerThread->getStopped())
{
timerThread = new MyThread(this, this);
timerThread->run();
}
}

void Client::slotSendToServer()
{
// неважно
}

void Client::slotConnected()
{
QTime time;
QString mess = time.currentTime().toString()+" Received the connected signal";
txtInfo->append(mess);

QSqlQuery query;
query.prepare("INSERT INTO client (Time, Event, Code) VALUES (:time, 'General', 'Received the connected signal');");
query.bindValue(":time",time.currentTime().toString());
query.exec();
}

Класс потока: mythread.cpp:
Код:
MyThread::MyThread(QObject *parent, Client *some_client)
: QThread(parent)
{
qDebug() << "New Thread";

temp_client = some_client;
stopped = false;
timerExist = false;
}

void MyThread::setStoppedToFalse()
{
stopped = false;
}
bool MyThread::getStopped()
{
return stopped;
}

void MyThread::run()
{
if (!stopped)
{
qDebug() << "Thread RUN";
if (!timerExist)
{
tcpTimer = new QTimer(this);
connect(tcpTimer, SIGNAL(timeout()), SLOT(slotConnectToHost()));
timerExist = true;
}
tcpTimer->start(5000);
}
}

void MyThread::stop()
{
qDebug() << "Thread STOP";
tcpTimer->stop();
stopped=true;
}

void MyThread::slotConnectToHost()
{
temp_client->tcpSocket->connectToHost(temp_client->host,temp_client->port);
if (temp_client->tcpSocket->waitForConnected())
this->stop();
}

main.cpp:
Код:
static bool createConnection()
{
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("localhost");
db.setDatabaseName("test");
db.setUserName("root");
db.setPassword("root");
if (!db.open())
{
qDebug() << "Cannot open database: " << db.lastError();
return false;
}
else qDebug() << "Open database OK";
return true;
}

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

        QFile file("config.xml");
QXmlInputSource source(&file);
QXmlSimpleReader reader;
SaxHandler handler;
reader.setContentHandler(&handler);
reader.parse(source);

if (!createConnection())
return -1;

Client client(handler.hostName, handler.portNumber);
client.show();
return app.exec();
}

Вновь буду благодарен Вашим советам.


Название: Re: Параметры сигналов/слотов
Отправлено: mutineer от Апрель 12, 2011, 00:50
Не понял идеи. Что именно должно выполняться в отдельном потоке?


Название: Re: Параметры сигналов/слотов
Отправлено: niklep от Апрель 12, 2011, 07:13
В отдельном потоке создается таймер и каждые 5 секунд по сигналу таймера timeout() происходит попытка подключения к серверу. И так пока не произойдет подключение. При этом GUI лагает как раз каждые 5 секунд...


Название: Re: Параметры сигналов/слотов
Отправлено: mutineer от Апрель 12, 2011, 07:36
Гуй лагает потому что таймер-то тикает в отдельном потоке, а вот слот, который по таймеру пытается подключиться, выполняется все еще в гуи потоке и при ожидании подключения замораживает гуй. Ну и не жди подключения waitForConnected, юзай сигналы сокета connected() и socketError()


Название: Re: Параметры сигналов/слотов
Отправлено: niklep от Апрель 12, 2011, 09:24
А вот я думал, что если я описал слот slotConnectToHost в класса MyThread, то он в новом потоке будет выполняться... А как тогда мне понимать, какие действия будут выполняться в новом потоке, а какие в старом?


Название: Re: Параметры сигналов/слотов
Отправлено: mutineer от Апрель 12, 2011, 09:43
В новом потоке выполняется метод run() объекта QThread, все вызванные непосредственно оттуда методы, а так же слоты объектов, которые были перемещены в поток при помощи
Код:
void QObject::moveToThread ( QThread * targetThread )
Более подробно почитай, например, тут: http://habrahabr.ru/blogs/qt_software/115830/ (http://habrahabr.ru/blogs/qt_software/115830/)