Blocking the event loop
-
I'm new to GUI programming. I've written a client side for a messaging application, but the text never updates in the chat window. I've been reading around different posts and I keep coming up with that I'm blocking the event loop, but I really don't understand how I am, which probably means I don't fully understand what the event loop is.
Here's the code I have for my MainWindow (where the loop is blocked).
The ui->textBrowser is what never updates.@MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
bool flag;
serverAddr = new QString;
serverPort = new int;
userName = new QString;
startDialog start(serverAddr, serverPort, userName,this);
start.setModal(true);
start.exec();flag = MainWindow::Connect(); ui->setupUi(this); if(!flag) { ui->textBrowser->setText("Unable to connect to server. Please restart client to try again."); } else { ui->textBrowser->setText("Successfully connected"); }
connect(socket,SIGNAL(readyRead()),this,SLOT(readyRead()));
connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(on_pushButton_clicked()));
}MainWindow::~MainWindow()
{
delete ui;
delete serverAddr;
delete serverPort;
delete userName;
delete socket;
}bool MainWindow::Connect()
{
socket = new QTcpSocket(this);
bool flag=false;
QString tempString;
socket->connectToHost(*serverAddr,*serverPort);
if(!socket->waitForConnected())
flag = false;
else flag=true;
if(flag)
{//attempt to register user with name
socket->write("REG\n");
socket->flush();
socket->waitForBytesWritten(3000);
socket->waitForReadyRead();
tempString = socket->readAll();
if(tempString == "ACK")
{
socket->write(userName->toUtf8());
socket->flush();
tempString.clear();
socket->waitForReadyRead();
tempString = socket->readAll();
if(tempString=="ACK")
{
return true;
}
else return false;
}
else return false;
}
else return false;
}void MainWindow::readyRead()
{
//client has now been told by the socket that there is data to be ready that is not a part of the sending or registering. cool
QString temp,browsTemp;
temp=socket->readAll();
ui->textBrowser->append(temp);
ui->textBrowser->repaint();
temp.clear();
}void MainWindow::on_pushButton_clicked()
{
//user clicked the send button and would like their message to be sent. woooo.
QString temp, stuff;
temp=ui->lineEdit->text();
socket->write("MSG\n");
socket->flush();
//socket->waitForBytesWritten(3000);
// socket->waitForReadyRead();
//socket->flush();
//stuff=socket->readAll();
//stuff=socket->readAll();//ready to send
socket->write(temp.toUtf8());
socket->flush();
ui->lineEdit->clear();
// socket->waitForReadyRead();
// stuff=socket->readAll();
// if(stuff!="ACK")
// {
// ui->textBrowser->clear();
// ui->textBrowser->setText("Send failed. Please close client to reconnect");
// }}@
Thanks
-
yes most of the QTcpSocket functions you're using are blocking. But all of them have a time limit...
For clarification:
You can see the event loop as a infinite loop in which every iteration sends the available events to it's receiver objects. Events may be Mouse events, keyboard events, paint events, timer events, etc.
Mostly you start the main event loop with QApplication.exec() und thus starting the engine of your application ;) -
Your code calls Connect() in the constructor. Connect() tries to implement a Qt networking conversation. Qt networking is inherently asynchronous and relies on event delivery via the event loop. UI redraw relies on the UI being visible and the event loop.
Your main() function presumably looks like this:
@
QApplication app(argc, argv);
MainWindow m;
m.show();
return app.exec();
@
All of your code is trying to happen at line 2, before the UI is shown and before the event loop is started at line 4. You have attempted to compensate using the blocking calls but really you need to move this processing until after the event loop is started and use the non-blocking methods. You can use a zero length timer set up in the constructor to call a slot immediately after the event loop starts processing. -
Would the issue be fixed if I created a new class that inherited QTcpSocket and used that for all the connection stuff in the MainWindow?
And what constructor would I be calling? The MainWindow constructor or the QTcpSocket constructor if I used the 0 length timer?
-
I think you don't need a new class, just split all the stuff you do in your connect funtion into more dedicated slots and don't use the syncronous methods from QTcpSocket.
For example in your connect function just do the connect call and instead of calling waitForConnected connect a new slot (you will have to implement this) to the connected() signal of the socket you use. Then in this new slot proceed with the next step you want to take. Repeat this pattern until everything you want your client to do is covered.
-
You don't need a new thread if you use all the asyncronous methods. You would just add a new layer of complexity to your application by using threads. It really isn't that much work to change the existing code you already have to the asyncronous approach. Its easier to debug this way and you will learn more about the event-driven nature of Qt, which you will have to do sooner or later.
To make the decision easier for you here's the first step:
@
void MainWindow::Connect()
{
socket = new QTcpSocket(this);
connect(socket, SIGNAL(connected()), this, SLOT(clientConnected()));
connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(handleSocketError(QAbstractSocket::SocketError)));
socket->connectToHost(*serverAddr,*serverPort);
}void MainWindow::clientConnected()
{
//you are now connected, continue with your execution
connect(socket, SIGNAL(bytesWritten(qint64), this, SLOT(handleBytesWritten(qint64))));
connect(socket, SIGNAL(readyRead(), this, SLOT(handleReadyRead())));
socket->write("REG\n");
socket->flush();
}void MainWindow::handleBytesWritten(qint64 a_bytesSize)
{
// handle the bytesWritten signal if you need to
}void MainWindow::handleReadyRead()
{
// handle the readyRead signal
// for example
QString tempString = socket->readAll();
if(tempString == "ACK" && !m_bUserNameSend )
{
socket->write(userName->toUtf8());
socket->flush();
// use a member variable to remember that you send the username if you need to
m_bUserNameSend = true;
}
else
{
if(tempString == "ACK" && m_bUserNameSend)
{
// DO THE NEXT STEP.
}
}
}void MainWindow::clientDisconnected()
{
// your client disconnected, handle this if you want to
}void MainWindow::handleSocketError(QAbstractSocket::SocketError a_error)
{
// something went wrong, check the SocketError and handle the error accordingly
}
@ -
BTW you don't need to send your own "ACK" acknowledgments, because if you listen to the socket signals error() and disconnected() you will immediately see if something went wrong and if you don't receive these signals you can assume that everything was received correctly. The TCP/IP protocol already handles the acknowledgments and all that stuff for you. But I guess you do this just for testing and as a learning exercise.
For more info about the signals you can already use see the docs for "QIODevice":http://qt-project.org/doc/qt-4.8/qiodevice.html and "QAbstractSocket":http://qt-project.org/doc/qt-4.8/qabstractsocket.html, which are inherited by QTcpSocket.
-
This project is for a networking class I'm in, so yes, the ACKs are just so I can see myself what's going on. I do know that TCP/IP takes care of all that for me.
And I really appreciate the help, KA510. I get what you mean about not using synchronous and to just use socket signal handling. It does make a lot more sense to me now. I'm working on it now.
-
Glad I could persuade you to join the asynchronous side ^^
And don't forget what ChrisW67 said! For example you could move the connect call out of your constructor and start it by clicking a button (connect the buttons clicked() signal to your slot connect() ) or use a timer ( "singleshot":http://qt-project.org/doc/qt-4.8/qtimer.html#singleShot ) to start the connect() as soon as the eventloop is running.TBH I'm not sure if you have to do this if all your stuff is using the asynchronous approach?