[Qt-interest] Why Bus error clientSocket->waitForConnected()?

Thiago Macieira thiago at kde.org
Mon May 3 18:31:48 CEST 2010


Em Segunda-feira 03 Maio 2010, às 16:28:09, Kustaa Nyholm escreveu:
> >> Why? Who needs a non blocking readline? Like I've already said several
> >> times I would love to see a use case where this is needed...
> > 
> > Because none of the QIODevice API is blocking, except for the waitFor
> > functions.
> 
> Yes, I got that a long time ago, how about the use case?

See here:

> > The design principle is that you can call any function and expect it to
> > return immediately. That makes the API usable from an event-driven
> > application in the GUI thread, without worrying that the GUI might
> > freeze because the socket hasn't received enough bytes.
> 
> So that [you can call any function and expect it to return immediately] is
> the overriding design consideration? Ok, this explains it, so it was just
> that readLine() design was hostage to this consideration?

The design decision is that methods don't block unless they are called 
"waitFor". Or, put in another way, the API is asynchronous: the actual sending 
and receiving of data happens in the event loop, when the socket notifiers 
indicate that there is data available or that it's clear for sending.

Unlike Java, Qt does not require the use of threads for using sockets.

Non-blocking sockets have been common-place in Unix systems for 20 years or 
more.

What's more, adding a "blocking mode" to the device classes was considered and 
discarded in the design process. It was deemed too complex to maintain and to 
understand, as well as prone to mistakes (using a blocking socket in the GUI 
thread).

> > Please stop reading Qt 3.3 documentation. It's irrelevant.
> 
> Sorry, I presumed that this would have been unchanged from version
> to version in the name of backwards compatibility. I'm actually using Qt
> 4.7 but routinely google for the API docs as I find it easier than using
> the Qt documentation and in my laziness I did not bother check the
> version.

Source and binary compatibility are not guaranteed across a major release (Qt 
4.0). Most of the behaviour was kept, however, but we did take the opportunity 
to clean up some mistakes and improve where things didn't work.

> > However, Qt 3.3 had the behaviour I wanted it to have in Qt 4. It was
> > changed in the 4.0 process and now we have to live with it.
> 
> Are you going to change this back?

Maybe in Qt 5.0, which is not scheduled at all, so it will not happen before 
2013.

I don't know why this specific change was made in Qt 4.0, but it was done and 
even documented as such.

> > The API design is perfectly good. The API is non-blocking and that was
> > intentional.
> 
> Only as far as your design criteria [non-blocking API] is satisfied,
> your users may feel and some actually do feel differently when it comes to
> readLine().
> 
> Everyone is entitled to their opinion I guess.

Of course.

> > If the device is a file or all data is available (such as a closed
> > socket,
> > 
> > finished process or finished QNetworkReply):
> >     QByteArray line;
> >     while (!(line  = device.readLine()).isNull())
> >     
> >         processLine(line);
> 
> For that to work, I as a coder, need to know and care if it is a file
> or close socket or what have you ... doesn't sound good. Surely I
> should be able to write code for something as simple as this that does
> not need to care.

Well, yes. If you find out that there's no more data available, you must be 
able to decide whether that means there will never be more data available 
(closed socket, finished reply, exited sub-process, etc.) or whether some more 
may arrive in the future.

And without blocking, otherwise your GUI would freeze.

> > Or if you insist in doing it the blocking way:
> Well, that was the requirement in the OP's problem and in
> many cases that makes perfect sense.

Indeed it does, but it's technically limited to one socket per thread. As soon 
as you need two, this breaks.

Also note that, unlike low-level BSD sockets, Qt takes care of writing more 
bytes to the socket while waiting for incoming bytes to arrive. That is, if 
both ends of the connection try to send big buffers to one another, with low-
level blocking sockets they'd deadlock: someone must read off the kernel buffers 
so that more bytes are received.

Qt's "blocking" waitForReadyRead() and waitForBytesWritten() APIs are done on 
top of non-blocking sockets and use a proper select(2) loop.

> >     while (device.waitForReadyRead(-1)) {
> >         while (device.canReadLine())
> >             processLine(device.readLine());
> >     }
> >     if (device.bytesAvailable())
> >         processLine(device.readLine()); /* device finished without a
> >         newline
> 
> Ok, more or less what I sketched, and demonstrates the main issue of
> having a non blocking readLine() in that the processLine needs to be
> presented twice preventing coding of that processing
> in-line unless I want to copy/paste the code. Not a big deal,
> but shows that from the readLine() point of view the API design
> leaves something to desire for, but I know understand that Qt's
> overall design goal is/was non blocking API.

Well, if *that* is your problem:

    bool deviceIsOpen = true;
    device.write(largeChunkOfData);
    while (deviceIsOpen || device.bytesAvailable()) {
        while (device.canReadLine() || 
               (!deviceIsOpen && device.bytesAvailable())
            processLine(device.readLine());

        if (deviceIsOpen)
            deviceIsOpen = device.waitForReadyRead(-1);
    }

This makes the loop run one more time, after the device disconnected. And 
after it has disconnected, processLine is called one more time after 
canReadLine() returns false (as long as there are still some bytes available).

Note the writing of a large chunk of data above. Like I explained before, 
waitForReadyRead() *also* writes to the device as the opportunity presents 
itself. If we had made a blocking readLine(), we'd have had to make it use 
waitForReadyRead() just like it was done above. Or like this:

QByteArray blockingReadLine(QIODevice *device)
{
    while (!device->canReadLine() && device->waitForReadyRead(-1))
        ;
    return device->readLine();
}

I'll leave the addition of a timeout parameter as an exercise to the reader 
(use QElapsedTimer). Note that waitForReadyRead() can return true but a line 
is still not readable.

With this function, your loop with a socket would be as simple as:

    do {
        processLine(blockingReadLine(socket));
    } while (socket->state() == QTcpSocket::ConnectedState);

Unfortunately, QIODevice::isOpen is not suitable for this condition. A socket 
can be open but disconnected, a reply can be open and finished, etc.

> > Please stop reading Qt 3.3 documentation. It's waitForReadyRead in Qt 4.
> 
> Ok, I see that the problems with 3.3 documentation have been mostly
> fixed in Qt 4 API docs. Great.

Good to know. :-)

-- 
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
  Senior Product Manager - Nokia, Qt Development Frameworks
      PGP/GPG: 0x6EF45358; fingerprint:
      E067 918B B660 DBD1 105C  966C 33F5 F005 6EF4 5358
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 190 bytes
Desc: This is a digitally signed message part.
Url : http://lists.qt-project.org/pipermail/qt-interest-old/attachments/20100503/95198366/attachment.bin 


More information about the Qt-interest-old mailing list