PDA

View Full Version : Asynchronous server msg vs synchronous functions



nouknouk
1st February 2006, 17:56
Hi.
First 'n in advance, sorry for my poor English, I'm french ;)

the context : I'm developping some kind of network layer in order to use IRC servers for doing point to multipoint communication between clients without the need of a dedicated server.

Here is my problem :

I would like to create a class IRCConnexion that holds a QSocket and manages every message sent to /received by the server.
This class will have functions like this one :
QDict<Channel>* getChannelList();

This function would do 3 things :
- emitting a request message for the channel list.
- getting each server's response and putting them into the QPtrList
- returning the QDict.

This function could be called in a function inside the main window like this :

ptr = getChannelList();
if ( ptr.find(a_channel_name) ) { do_something; }

But, as this kind of request takes a while, I would like to let the main window having a chance to process other things while the list is downloaded from the server (like repainting, processing events...)

As far as I know, we can't instanciate QSocket in another thread than the main one.

How can I do that kind of thing ?

Many thanks in advance if you take a little time to give me ways to explore :)

jacek
1st February 2006, 18:42
You don't have to use multithreading, instead you can use signals & slots mechanism. Just make your application fully event-driven and don't block the event loop.

Note that in Qt3 you can't use QSocket in a non-GUI thread, because it requires an event loop. So if you want to have multiple threads, you will have to use QSocketDevice instead.

nouknouk
1st February 2006, 19:25
Just make your application fully event-driven and don't block the event loop.


Do you mean I should write a getChannelList() like this :
QPtrList<Channel> getChannelList() {
while (!get_list_finished) {
do some job...
(QApplication::eventLoop())->processEvent();
}
return(the_QPtrList);
}

If it's what you said, what will be the limitations of that kind of code ? I mean, will QSocket continue to react on new incoming data while the channel list retieving ? Will the user be able to interact with the interface ?
And if the user can still interact with the interface, what would happen if we call simultaneously two times the function ?

Anyway, many thanks for this tips :)

PS: sorry for those newbie questions, but i'm new in qt development even if I'm coding in c++ since a while.

nouknouk
1st February 2006, 20:00
I've made a test : it works fine :-)

The only thing I would like to know is if there some limitations, especially with multiple simultaneous calls on the same function.

Anyway, many, many thanks :-)

jacek
1st February 2006, 22:15
Do you mean I should write a getChannelList() like this :

QPtrList<Channel> getChannelList() {
while (!get_list_finished) {
do some job...
(QApplication::eventLoop())->processEvent();
}
return(the_QPtrList);
}
Well, I thought about something more like:
void SomeClass::channelReceived( SomeType whatever ) // slot
{
add channel to the list
if ( list complete ) emit channelListReady( channelList );
}

void SomeClass::channelListReady( SomeList channelList ) // slot
{
do something with that list;
}

nouknouk
2nd February 2006, 09:50
The solution with signals and slots is the one I already have coded :)

But, for me, it's not a good one, because for some type of request it would be too complex to handle :
Maybe my first example wasn't relevant enough. Here is another one : a function like isUserConnected(QString nickName) : that function could be called in several different contexts and having 'to cable' signals and slots each time I need to know if a user exists is (to my mind) too complex and would result in totally unreadable code.

That's why I think that the solution with processEvents() is a good one. Due to this technology, I think that I can't start more than one request at a time (*). So I've to write some kind of 'request queue'.
What do you think of that ?

--------------------------------------------------------------------------
(*) because if there is two imbriqued requests, the first called can be finished as long as the second one has been finished before. Here is an exemple (is it correct ?) of what should be the call stack of my app if a user make two requests a the same time : a getChannelList() and before its end, a isUserConnected() :

onRefreshList() slot called when the user click a "refresh channel list" buttton
-->getChannelList() (#1)
---->while (!finished) processEvents() (#2)
------>onSearchUser() slot called when the user clicks a "search user" button
-------->isUserConnected() (#3)
---------->while (!finished2) processEvents() (#4)

At this time. If the channel list request is finished, the program wont iterate the (#2) until isUserConnected (#3) has returned. Am I right ?
--------------------------------------------------------------------------

nouknouk
2nd February 2006, 15:24
Well, It works almost fine, but there's still a (little) issue :

First : here is a sample code of my getChannelListNOW() function:



IRCChannelDict& IRCServer::getChannelListNOW() {
_channelListUpdateOk = false;
sendLineThruSocket("LIST");
while(!_channelListUpdateOk) {
QApplication::eventLoop()->processEvents(QEventLoop::AllEvents);
}
return _channelDict;
}
// _channelListUpdateOk is set to true by the socket message parser
// when it detects the end of the channel's list transmission.


Another similar function is getTimeOfServerNOW().

In my interface, i've got two buttons connected to two main window's slots in order to make a call of those functions.

I tried to click very fast on getTimeOfServer and immediatly after on getChannelList.
As the getTimeOfServerNOW is much quicker (less than 500ms), It should respond BEFORE getChannelListNOW() (2 or 3 seconds).
But it don't : in this test, I get the opposite : TimeOfServer respond AFTER ChannelList.

I think it's due to the call stack (see below), the first while statement can't be reached until getChannelListNOW() has ended.



onGetTimeOfServer() // slot called by the appropriate button.
getTimeOfServer() (#1)
while (!finished) processEvents() // inside getTimeOfServer() func
onGetChannelList() slot called inside 'processEvent'
getChannelListNOW() (#3)
while (!finished2) processEvents() (#4)


In other words, if more than one such request is called at a time, all the request will return only when the longest one has finished. This is not really a big problem for me, but do you have an idea on how to bypass this problem ?

Anyway, many thanks for your 'event' tip :)

jacek
2nd February 2006, 15:40
Since you are using a single socket you could create an object that mimics IRC server.

class IRCServer : public QObject
{
Q_OBJECT
public:
// ...

public slots:
void listChannels();
// other IRC commands

signals:
void channelFound( IRCChannel & ); // or channelsFound( IRCChannelList& )
// ...
};Methods like listChannels() would send commands to the server and when server sends a response you will would have to parse it and emit a proper signal. This should eliminate the need for qApp->processEvents().

nouknouk
2nd February 2006, 16:15
That's what I already have coded.
But for some functions I need somethings that look like an immediate response.

Indeed, for example a function isUserConnected(QString nickname).

isUserConnected would be called in a lot of different contexts (for different functions)

If I write those :
void checkUserConnected(QString nick); (equivalent to listChannels in your example)
userConnectedResponse(bool); (the signal, equivalent to channelFound)

I will have, for each function that would use checkUserConnected, to implement two functions.
1/ The first part of the code (before I need to check the user) in the first function
2/ The second part of the code (after checking) in a second function (ie, a slot).

That don't look really 'beautiful'. I will quickly get enormous interfaces for my classes.
Moreover, if I want to use the checkUser func again, in another part of the code , I will have to connect / disconnect signals and slots at each call :-/

That don't look really nice for me. Even more if you know that this 'framework' has to be easy reusable...

Don't you think so ?

jacek
2nd February 2006, 18:10
I will have, for each function that would use checkUserConnected, to implement two functions.
1/ The first part of the code (before I need to check the user) in the first function
2/ The second part of the code (after checking) in a second function (ie, a slot).
Maybe you could cache this information and add a QTimer to update the cache from time to time?


That don't look really nice for me. Even more if you know that this 'framework' has to be easy reusable...

Don't you think so ?
I guess it all depends on the logic you want to implement. Event-driven approach doesn't work well with all kinds of programs.