PDA

View Full Version : QUdpSocket: sending and receiving on the same socket?



mhoover
5th June 2009, 03:20
Hi,

I'm trying to implement a command client / command server architecture using QUdpSocket. So far I can send QStrings and the server gets them, but when I try to send status responses back nothing happens.

So when I send the command: "HADRONCOLLIDER5 GENERATE_STRANGELETS AMOUNT=DELUGE"

I'd like to get back: "0 0 0 GENERATE_STRANGELETS AMOUNT=DELUGE EXTREME_DANGER".

But I never get that return string.

Can I send the commands to the server and read responses from the server over the same port? Or is this crazy?

The command client is waiting for a readyRead() signal from the QUdpSocket.

mhoover
5th June 2009, 04:00
Ah ha.

I forgot that if you are *receiving* data, you have to bind to the port.

I added the .bind() and everything seems to be working.

Except one of our buildings seems to be turning into strange matter.

mhoover
5th June 2009, 21:09
Oops, it isn't working after all.

I thought I was getting a response, but what I was actually getting was the command I was sending the first time.

In other words, if I'm listening for a command when I send it, I get the command that I send. Is there anyway around this?

I tried disconnecting the readyRead() signal and reconnecting immediately after I send it, but it doesn't seem to fix it.

wysota
5th June 2009, 21:49
How exactly do you send the datagrams? And to what address?

mhoover
6th June 2009, 02:07
I've tried sending the datagrams a couple ways.

1) From a class that is waiting for a readReady() response, but if it was the first one to bind to the port it catches its own response. If it was the second class to bind to the port, it sends the command, but it cannot get the response. Sounds like a turf war.

2) From a class that sends udpSocket.write() and then immediately calls a waitForReadyRead() but never gets the response. Again, depending on whether the server binds first, it either sends the command and gets no response or the response fails to reach the server.

For the bind() I am using QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint. For the datastreams I am using QIODevice::ReadWrite.

mhoover
6th June 2009, 02:09
It would be nice if there were a udp "echo" example.


I have seen this one (not using Qt classes):

http://gnosis.cx/publish/programming/sockets2.html

And this Qt example, but I haven't been able to find all the code:

http://www.archivum.info/qt-interest@trolltech.com/2007-09/msg00367.html

mhoover
6th June 2009, 02:38
Dang, I've tried using the QAbstractSockets and that doesn't help either.

I was thinking that if I avoided a 'bind' neither side would lock the readyRead() signals all for itself.

wysota
6th June 2009, 07:38
Can we see your actual code?

mhoover
8th June 2009, 20:16
I realize this is not the best way to structure the code. I should use returns instead of nesting the if's more. I've been in kind of a hurry.

client side:

QUdpSocket udpSocket;
QByteArray datagram;
QDataStream out( &datagram, QIODevice::ReadWrite );
out.setVersion( QDataStream::Qt_4_5 );
out << ui.command_lineEdit->text();
udpSocket.connectToHost( QHostAddress::LocalHost, 5825 );
cout << "sending manual command: " << ui.command_lineEdit->text().toAscii().data() << endl;
bool result = udpSocket.waitForConnected( 3000 );
if ( result != false ) {
qint64 writtenSize = udpSocket.write( datagram ); // send command
if ( writtenSize != -1 ) {
result = udpSocket.waitForReadyRead( 3000 );
if ( result != false ) {
cout << "found a response." << endl;
while ( udpSocket.hasPendingDatagrams() ) {
QByteArray datagram(command_socket_device.bytesAvailable(), '0' );
command_socket_device.readDatagram(datagram.data() , datagram.size());
QString the_return;

QDataStream in( &datagram, QIODevice::ReadWrite);
in.setVersion( QDataStream::Qt_4_5 );
in >> the_return;
this->statusBar()->showMessage( the_return );

}
} else {
cout << "No response." << endl;
}
}
}

server side:
(called in the constructuor after a 1 sec delay)


while ( !done ) {

if ( command_socket_device.hasPendingDatagrams() == true ) {
cout << "found something to read." << endl;
QString command( "" );

QByteArray datagram(command_socket_device.bytesAvailable(), '0' );

command_socket_device.read(datagram.data(), datagram.size());

QDataStream in( &datagram, QIODevice::ReadWrite);
in.setVersion( QDataStream::Qt_4_5 );
in >> command;

cout << "Received: " << command.toAscii().data() << endl;
ui.log_textEdit->append( command );

if ( command == "HADRON_STARTUP" ) {
command.prepend( "0 0 0 " );
QByteArray datagram2;
QDataStream out( &datagram2, QIODevice::ReadWrite );
out.setVersion( QDataStream::Qt_4_5 );
out << command;
cout << "sending response: " << command.toAscii().data() << endl;
command_socket_device.write( datagram2 );
ui.log_textEdit->append( command );
}
cout << "Should have logged something." << endl;
}
qApp->processEvents();
}

I've tried a lot of combinations binding and not binding to the port.

Also, this is the blocking version I tried. I've also tried a version based on the more normal "readyRead".

wysota
8th June 2009, 21:46
What if you don't call connectToHost()?

mhoover
8th June 2009, 22:18
If I don't call connectToHost() on the server side, the client still catches it's own datagrams (regardless of which comes up first -client or server).

If I don't call connectToHost() on the client side, the server never gets the datagram (regardless of which comes up first).

If I don't call connectToHost() on either client or server, the server never receives the messages and the client doesn't get a response (regardless of which comes up first).

No bind() calls were made in these classes either.

mhoover
8th June 2009, 22:28
I get the sense that Qt doesn't really have much support for QUdpSockets binding to the same address. Just binding to the same port fails on Windows. See here:

http://www.qtsoftware.com/developer/task-tracker/index_html?method=entry&id=216193

It looks like the developers rejected this request because it's 'supposed' to fail.

Which seems strange because 1) the API has an explicit 'share address' flag and 2) this gets done in UDP socket programming a lot.

wysota
8th June 2009, 23:41
Binding two sockets to the same port is different than using one socket for reading and writing. The latter should work fine and Qt has no chance of breaking it so I suppose you are doing something wrong. The former should fail and so it does.

Try to replace your read() and write() calls with readDatagram() and writeDatagram().

mhoover
9th June 2009, 23:48
Binding two sockets to the same port is different than using one socket for reading and writing.

Yes, that is true.

I saw the code fragment of a Qt UDP echo server, and I thought that meant Qt could facilitate two agents writing (at different times) to the same port. Now it looks like that is not the case.

I tried readDatagram/writeDatagram first. I can't readDatagram without binding, and if I bind, no other agent can read it.

My plan is to use two Udp ports: one to send the command, and one for a return status.

Thanks for your help, wysota.

wysota
10th June 2009, 06:51
I can't readDatagram without binding, (...)

Why so? It is meant to be used without binding.

mhoover
17th June 2009, 03:21
Why so? It is meant to be used without binding.

Ahh ... connectToHost works for reading from the client side.

Thanks.

mhoover
17th June 2009, 03:28
I finally got this to work. My whole approach was mistaken.

I tend to disregard parameters in Qt functions the further along I get to the right of the parameter list. This is probably a bad practice.

Apparently when you call sendDatagram() (or readDatagram()) you can get the port number of the other socket.

So to do a simple echo server, I just had a server bind to a port. When it got readyReads from the client it captured the port and host on the stack and then resent the information to the client.

Meanwhile I had the client waiting on a readReady() so it could catch the response. Another thing that worked for me was to have a convenience blocking send function that used 'socket->waitForReadyRead( 3000 )' and then did a while loop on socket->hasPendingDatagrams().

I still think it would help a lot for the Qt folks to put a Udp echo server/client example in their network examples.

It turned out to be pretty simple, though.

Like Columbus' egg.