What's the best way to delete a QNetworkReply? - c++

I'm writing an embedded RESTful API client using the Qt5 Embedded layer of OpenEmbedded project. I want my client to be able to send a simple async request to notify my server on what's going on and an other scenario is that my client needs to synchronize data from the server.
To do so I wrote two functions, one to send the request and another to get the response. So if I don't care about the response, I only use the first one.
To be able to use the response with the second one, I use a QNetworkReply * as a member of my class. To keep the QNetworkReply alive, I also set the QNetworkAccessManager as a member of my class.
#include <QtNetwork>
class ApiClient : public QObject
{
Q_OBJECT
public:
ApiClient(QObject *parent = 0);
private:
QString host;
QString key;
quint32 replyTimeout;
QNetworkAccessManager manager;
QNetworkReply *reply;
void sendRequest(const QString &method, const QVariantMap &params = QVariantMap());
QVariantMap getResponse() const;
};
The apiclient.cpp file:
#include "apiclient.h"
ApiClient::ApiClient(QObject *parent) : QObject(parent)
{
}
void ApiClient::sendRequest(const QString &method, const QVariantMap &params)
{
QUrl url(QString("%1/%2/").arg(host).arg(method));
QUrlQuery query;
query.addQueryItem("key", key);
if (!params.empty())
{
QMapIterator<QString, QVariant> it(params);
while (it.hasNext())
{
it.next();
query.addQueryItem(it.key(), it.value().toString());
}
}
url.setQuery(query);
qDebug() << "url: " << url.toString();
reply = manager.get(QNetworkRequest(url));
}
QVariantMap ApiClient::getResponse() const
{
if (!reply)
{
qFatal("No request sent!");
}
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
timer.start(replyTimeout);
loop.exec();
if (timer.isActive())
{
timer.stop();
if (reply->error())
{
qFatal("Wrong reply!");
}
int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (code != 200)
{
qFatal("Invalid server response!");
}
QJsonDocument result = QJsonDocument::fromJson(reply->readAll());
if (result.isNull())
{
qFatal("Invalid JSON!");
}
reply->deleteLater();
return result.object().toVariantMap();
}
disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
reply->abort();
reply->deleteLater();
return QVariantMap();
}
Is that a good way to proceed? How should I manage the QNetworkReply pointer when other signals are emitted (i.e. error(), sslErrors(), ...)?

QNetworkReply will always emit finished(), even when an error occured.
deleteLater() could even be called in a slot connected to that signal, so that part should be fine.
But I would recommend to look into a more asynchronous approach of handling the request, nested event loops like the on in your getResponse() can lead to "interesting" behavior, because you can basically get into re-entrancy situations in a single threaded program.

Related

QNetworkAccessManager never emits finished() signal

I'm working on a module for a project where HTTP GET requests are used to retrieve some XML data, which is then converted to another format and send to a sub-system.
The code I have written so far is below:
CMakeLists.txt:
project(HttpDemo)
cmake_minimum_required(VERSION 2.8)
set(CMAKE_BUILD_TYPE Debug)
#find_package(Qt5Widgets)
find_package(Qt5Core)
find_package(Qt5Network)
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
qt5_use_modules(${PROJECT_NAME} Core Network) #Gui Widgets
main.cpp
#include <QtCore>
#include <QtNetwork>
class HttpHandler : public QObject
{
Q_OBJECT
public:
HttpHandler(QObject* parent=Q_NULLPTR) : QObject(parent)
{
QObject::connect(&nm, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
qDebug() << QSslSocket::sslLibraryBuildVersionString();
}
private:
QNetworkAccessManager nm;
public slots:
void post(QString urlLink)
{
QUrl url(urlLink);
QNetworkRequest request(url);
QSslConfiguration sslConf;
sslConf.setProtocol(QSsl::SslV3);
request.setSslConfiguration(sslConf);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencded");
QUrlQuery query;
query.addQueryItem("client_id", "1234");
query.addQueryItem("code", "abcd");
QUrl params;
params.setQuery(query);
nm.post(request, params.toEncoded());
}
void get(QString urlLink)
{
QUrl url(urlLink);
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
nm.get(request);
}
void replyFinished(QNetworkReply* reply)
{
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if(statusCode.isValid())
{
// Print or catch the status code
QString status = statusCode.toString(); // or status_code.toInt();
qDebug() << status;
qDebug() << QString::fromStdString(reply->readAll().toStdString());
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
HttpHandler hh;
hh.get("SOME_URL");
return a.exec();
}
#include "main.moc"
With SOME_URL I have tried a lot of links all of which work without any issues in let's say the Http Requester addon for Firefox. I get:
"OpenSSL 1.0.1j 15 Oct 2014"
qt.network.ssl: QSslSocket: cannot resolve SSLv2_client_method
qt.network.ssl: QSslSocket: cannot resolve SSLv2_server_method
According to the authority called the Internet this shouldn't be a problem. One thing's for certain - my replyFinished(QNetworkReply*) slot doesn't get triggered although it is connected to the finished() signal of the QNetworkAccessManager. This means that whatever the reason the signal is not emitted. Changing the QSslConfiguration to a different QSsl::SslProtocol doesn't make a difference in the outcome.
UPDATE (as requested in the comments):
Following code uses readyRead() and dynamically allocated reply. The result - same as above.
#include <QtCore>
#include <QtNetwork>
class HttpHandler : public QObject
{
Q_OBJECT
public:
HttpHandler(QObject* parent=Q_NULLPTR) : QObject(parent)
{
qDebug() << QSslSocket::sslLibraryBuildVersionString();
this->manager = new QNetworkAccessManager(this);
this->reply = Q_NULLPTR;
}
private:
QNetworkAccessManager* manager;
QNetworkReply* reply;
signals:
void finished();
public slots:
void post(QString urlLink)
{
QUrl url(urlLink);
QNetworkRequest request(url);
QSslConfiguration sslConf;
sslConf.setProtocol(QSsl::SslV2);
request.setSslConfiguration(sslConf);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QUrlQuery query;
query.addQueryItem("client_id", "1234");
query.addQueryItem("code", "abcd");
QUrl params;
params.setQuery(query);
manager->post(request, params.toEncoded());
}
void get(QString urlLink)
{
QUrl url(urlLink);
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
this->reply = manager->get(request);
QObject::connect(reply, SIGNAL(readyRead()), this, SLOT(slotReadyRead()));
}
void slotReadyRead()
{
qDebug() << "Hello"; // I never land here
}
void replyFinished(QNetworkReply* reply)
{
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if(statusCode.isValid())
{
QString status = statusCode.toString(); // or status_code.toInt();
qDebug() << status;
qDebug() << QString::fromStdString(reply->readAll().toStdString());
}
emit finished();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
HttpHandler *hh = new HttpHandler(&a);
QObject::connect(hh, SIGNAL(finished()), &a, SLOT(quit()));
hh->get("http://httpbin.org/ip"); // or any other httpbin.org endpoint
return a.exec();
}
#include "main.moc"
UPDATE 2:
I just found an example in the Qt documentation. Downloaded, compiled and ran the thing - same error but it works.
Issue resolved (see here). Basically the problem was the company's proxy. A colleague of mine gave it a shot and replaced the HTTP with HTTPS (even though the link is HTTP) and it worked all of a sudden. Then it struck us - the company's proxy caches HTTP (and does other things too), which leads to huge delays and if the timeout tolerance is small enough QNetworkAccessManager will return a socket timeout.
Using QNetworkProxyFactory::setUseSystemConfiguration(true) enables the proxy in a way that doesn't make your application dependent on a configuration in your code but rather the configuration of the system.

Getting a page content with Qt

I am trying to get the content of a HTTP request into a QString variable with Qt and C++
QNetworkAccessManager networkManager;
QUrl url("https://someurl.test.com/this-actually-exists");
QNetworkRequest request;
request.setUrl(url);
QNetworkReply* currentReply = networkManager.get(request); // GET
QString reply = QTextCodec::codecForMib(1015)->toUnicode(currentReply->readAll());
Still, the variable reply seems to stay empty. Obviously, I misunderstand the documentation. How do I get this to perform?
You can use two different ways even the synchronous or asynchronous ways to do this. The asynchronous way is :
connect (&networkManager , SIGNAL(finished(QNetworkReply*)) ,this, SLOT(done(QNetworkReply*)));
networkManager.get(request);
And you should read the contents from the returned reply in the slot connected to finished signal in the following way :
void net::done(QNetworkReply * reply)
{
if (reply->error() == QNetworkReply::NoError)
{
data = QString(reply->readAll ());
}
else
{
data = QString(reply->errorString ());
}
}
The synchronous way is like :
QNetworkReply *reply = networkManager.get(request);
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), &loop, SLOT(quit()));
loop.exec();
QByteArray bts = reply->readAll();
QString str(bts);
Here you use an event loop to wait until the reply is finished and then read the available bytes and get the string.
I need to assume you're running an application with an event-loop in place? If not, then it's a bit harder...
If so, replace your last line that builds the reply QString:
connect(currentReply, SIGNAL(finished()), this, SLOT(gotAReply()));
Then you'll have to define another method in your class as a slot that gets triggered as soon as that reply got filled:
void gotAReply()
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(QObject::sender());
if (reply)
{
if (reply->error() == QNetworkReply::NoError)
{
QString replyText( reply->readAll() );
}
reply->deleteLater();
}
}
Don't forget: for Signals and Slot to work your class declaration must contain the Q_OBJECT macro.

QNetworkAccessManager issue

QString My_class::My_Method()
{
QNetworkAccessManager *manager= new QNetworkAccessManager(this);
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(ReplayFinished(QNetworkReply*)));
manager->get(QNetworkRequest(QUrl(My_URL)));
return str;
}
void My_class::ReplayFinished(QNetworkReply *replay)
{
QString buffer;
if(replay->isOpen())
{
buffer=replay->readAll();
//treatment on the buffer and the public Qstring 'str'(declared in My_class.h) is updated
}
}
Hi,
the problem is that when calling the My_method() in the main, the QString str is empty because it was returned that way without waiting for the ReplayFinished to update it.
What could be the solution to wait until the job is complete so I can get the proper information, not skipping it and returning something else.
Thank you.
You can use an event loop to wait until the reply is finished and then read the available bytes and return the string :
QString My_class::My_Method()
{
QNetworkAccessManager manager;
QNetworkReply *reply = manager.get(QNetworkRequest(QUrl(My_URL)));
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), &loop, SLOT(quit()));
loop.exec();
QByteArray bts = reply->readAll();
QString str(bts);
delete reply;
return str;
}

Qt request never trigger the finished() signal

i have something really strange i have this code :
i think i know what is wrong but i dont know how to fix it .
this is what i have :
when i put break point in int test = 0;
it getting there before it gets to httpFinished() slot in the HttpClient , mybe this is the problem ?
in the main.cpp
---------------------------------------------------------------------------------------------------------
#while (i.hasNext())
{
i.next();
ThreadWorker* pThreadWorker = new ThreadWorker();
pThreadWorker->setUrl(sUrl);
QThreadPool::globalInstance()->start(pThreadWorker);
}
QThreadPool::globalInstance()->waitForDone();
---------------------------------------------------------------------------------------------------------
void ThreadWorker::run()
{
startWork();
}
void ThreadWorker::startWork()
{
m_pHttpClient = new HttpClient();
m_pHttpClient->startRequest(m_url);
int test = 0;
}
--------------------------------- HttpClient based on the http example from Qt -----------------------------------
HttpClient::HttpClient()
{
m_networkManager = new QNetworkAccessManager(this);
connect(m_networkManager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
this, SLOT(slotAuthenticationRequired(QNetworkReply*,QAuthenticator*)));
#ifndef QT_NO_OPENSSL
connect(m_networkManager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)),
this, SLOT(sslErrors(QNetworkReply*,QList<QSslError>)));
#endif
}
void HttpClient::startRequest(QUrl url)
{
m_url.setUrl("http://qt.nokia.com/");
QNetworkRequest request;
request.setUrl(m_url);
reply = m_networkManager->get(request);
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(slotError(QNetworkReply::NetworkError)));
connect(reply,SIGNAL(finished()),
this, SLOT(httpFinished()));
connect(reply, SIGNAL(readyRead()),
this, SLOT(httpReadyRead()));
connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
this, SLOT(updateDataReadProgress(qint64,qint64)));
}
the httpFinished() function that is under private slots: never triggered , why ?
UPDATED THE QUESTION
Since the HttpClient and QNetworkAccessManager objects are created within the thread, they automatically belongs to that thread (see QObject::moveToThread), and they both needs an event loop running in that thread, for QNAM to do any work at all, and for your QObject derived class to be able to execute the slots.
You could add a call to QThread::exec() in run() to run that event loop (if you were using QThread):
void Thread::run()
{
startWork();
exec();
}
or create and start a QEventLoop whose quit() slot has to be connected somewhere to stop the loop (for example a finished() signal in the class HttpClient that you would emit when the work is done):
void ThreadWorker::run()
{
startWork();
QEventLoop loop;
QObject::connect(m_pHttpClient, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
}
Also, since Qt 4.8, QNetworkAccessManager is multithreaded, so you might not need to use threads yourself.

Server thread inside a qt class (need mutex?)

I made this server class that starts a thread when new connection comes in. It works ok with some cases, but it's not very stable. I am trying to solve where it breaks. My debugger tells me something about qmutex. If anyone can spot the problem. ty
It connects with parent with signal&slots and gets data back also.
Here is the header:
#ifndef FORTUNESERVER_H
#define FORTUNESERVER_H
#include <QStringList>
#include <QTcpServer>
#include <QThread>
#include <QTcpSocket>
#include <string>
using namespace std;
class FortuneServer : public QTcpServer
{
Q_OBJECT
public:
FortuneServer(QObject *parent = 0);
public slots:
void procesServerString(string serverString);
void getStringToThread(string serverString);
protected:
void incomingConnection(int socketDescriptor);
private:
QStringList fortunes;
signals:
void procesServerStringToParent(string serverString);
void getStringToThreadSignal(string serverString);
};
class FortuneThread : public QObject
{
Q_OBJECT
public:
FortuneThread(int socketDescriptor, QObject *parent);
public slots:
void getString();
void sendString(string sendoutString);
signals:
void error(QTcpSocket::SocketError socketError);
void fromThreadString(string serverString);
void finished();
private:
int socketDescriptor;
QString text;
QTcpSocket tcpSocket;
};
#endif
and cc:
#include <stdlib.h>
#include <QtNetwork>
#include "MeshServer.hh"
#include <iostream>
#include "TableView.hh"
using namespace std;
FortuneServer::FortuneServer(QObject *parent)
: QTcpServer(parent)
{
}
void FortuneServer::procesServerString(string serverString){
emit procesServerStringToParent(serverString);
}
void FortuneServer::getStringToThread(string serverString){
emit getStringToThreadSignal(serverString);
}
void FortuneServer::incomingConnection(int socketDescriptor)
{
FortuneThread *serverthread = new FortuneThread(socketDescriptor, this);
//connect(&serverthread, SIGNAL(finished()), &serverthread, SLOT(deleteLater()));
QThread* thread = new QThread;
serverthread->moveToThread(thread);
connect(thread, SIGNAL(started()), serverthread, SLOT(getString()));
connect(serverthread, SIGNAL(fromThreadString(string)), this, SLOT(procesServerString(string)));
connect(this, SIGNAL(getStringToThreadSignal(string)), serverthread, SLOT(sendString(string)));
connect(serverthread, SIGNAL(finished()), thread, SLOT(quit()));
connect(serverthread, SIGNAL(finished()), serverthread, SLOT(deleteLater()));
connect(serverthread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
FortuneThread::FortuneThread(int socketDescriptor, QObject *parent)
: QObject(parent), socketDescriptor(socketDescriptor)
{
}
void FortuneThread::getString()
{
if (!tcpSocket.setSocketDescriptor(socketDescriptor)) {
emit error(tcpSocket.error());
cout<<"socket error"<<endl;
return;
}
//in part
if(!tcpSocket.waitForReadyRead(10000)){
emit finished();
return;
}
int joj = tcpSocket.bytesAvailable();
char inbuffer[1024];
tcpSocket.read(inbuffer,1024);
string instring;
instring = inbuffer;
instring.resize(joj);
emit fromThreadString(instring);
}
void FortuneThread::sendString(string sendoutString)
{
//out part
char buffer[1024];
int buffer_len = 1024;
int bytecount;
memset(buffer, '\0', buffer_len);
string outstring = sendoutString;
int TempNumOne= (int)outstring.size();
for (int a=0;a<TempNumOne;a++)
{
buffer[a]=outstring[a];
}
QByteArray block;
block = buffer;
tcpSocket.write(block);
tcpSocket.disconnectFromHost();
tcpSocket.waitForDisconnected();
emit finished();
}
this is from parent:
//server start
QHostAddress adr;
adr.setAddress( QString("127.0.0.1") );
adr.toIPv4Address();
quint16 port = 1101;
if (!server.listen( adr, port)) {
QMessageBox::critical(this, tr("CR_bypasser"),
tr("Unable to start the server: %1.")
.arg(server.errorString()));
close();
return;
}
QString ipAddress;
ipAddress = server.serverAddress().toString();
statusLabel->setText(tr("The server is running on\n\nIP: %1\nport: %2\n\n"
"Run the Fortune Client example now.")
.arg(ipAddress).arg(server.serverPort()));
connect (&server, SIGNAL(procesServerStringToParent(string)), this, SLOT(procesServerString(string)));
connect (this, SIGNAL(StringToServer(string)), &server, SLOT(getStringToThread(string)));
edit: what I am trying to do:
I have a client (part of a game engine(Cryengine)) that I made to send string of ingame coordinates and some other things with a socket like its done in a link I gave before. This works ok. I get data on "127.0.0.1" port 1101. Now I just need this data to be evaluated in my own program, that has this TableView class, inside which I can collect coordinates I get from the string, callculate some data from coordinates and then return this new string back through the server to gameengine. In game I will click on objects get their coor., make a string out of that (containing coor,entityid, etc..), send this string to server, that returns callculated info from TableView. I just need this one way flow only one client that is sending strings. I am not sure about recv(hsock, buffer, buffer_len, 0), I guess node that is responsible for string sending in game will wait for return string? This is one of my first programs atm I am realy confused...
The code you present is exemplary of cargo cult coding: you do various unnecessary things, apparently in hopes of fixing the problem.
The Likely Crasher ...
There are tons of problems with the code, but I think the cause of the crash is this: tcpSocket.write(block) does not send out a zero-terminated string down the wire. The block is zero-terminated, but the assignment to a byte array does not add this zero termination to the size() of QByteArray. The following code prints 1, even though there is a zero terminating byte internally in the contents of the byte array.
QByteArray arr = "a";
qDebug("len=%d", arr.size());
The receiving code expects the zero termination, but never receives it. You then proceed to assign a non-zero-terminated buffer to std::string:
string instring;
instring = inbuffer;
instring.resize(joj);
The subsequent resize is cargo cult: you're trying to fix the problem after std::string & std::string::operator=(const char*) has already read past your buffer, in all likelihood.
Do not take this to mean that fixing just that is the right way to proceed. Not at all. The right way to proceed is to delete the code you wrote and do it right, without a ton of unnecessary incantations that don't help.
... and All The Other Problems
You've fallen into the trap of believing in magic, perpetuated endlessly in various forums.
The threads are not magical objects that you can just apply to any problem out there in hopes that they help. I don't know what makes people think that threads are magical, but the rule of thumb is: If someone tells you "oh, you should try threads", they are most likely wrong. If they tell that in relation to networking, they are pretty much never right, they are unhelpful, and they don't understand your problem at all (neither do you, it seems). More often than not, threads will not help unless you clearly understand your problem. Qt's networking system is asynchronous: it doesn't block the execution of your code, if you don't use the waitxxxx() functions. You shouldn't use them, by the way, so all is good here. No need for a bazillion threads.
So, it is completely unnecessary to start a new thread per each incoming connection. It will decrease the performance of your server -- especially if the server does simple processing because you add the overhead of context switching and thread creation/dismantling to each connection. You want less than 2 threads per each core in your system, so using QThread::idealThreadCount() for the number of threads in the pool would be a good starting point.
You are also depriving yourself of the benefit of threading since you use the networking thread only to receive the data, and you then send out a fromThreadString(string) signal. I presume that signal is sent to your application's main thread. Now that's just silly, because receiving a bunch of bytes from a network socket is downright trivial. Your threads don't do any work, all the work they do is wasted on their creation and removal.
The code below is a simple example of how one might correctly use the Qt APIs to implement a client-server system that distributes work across the physical cores in a round-robin fashion. It should perform quite well. The Fortune client example included in Qt is very unfortunate indeed, because it's precisely the wrong way to go about things.
What one will notice is:
It's not entirely trivial. Qt could be more helpful, but isn't.
Both the clients and the senders are moved into threads from a thread pool.
Disconnected clients are not deleted, but merely returned to a list of clients
kept by the tread pool. They are reused when a client is called for.
QThread is not derived from. QTcpServer is only derived to access the socket handle.
No functions whose name begins with wait() are used. Everything is handled asynchronously.
The ThreadPool keeps a looked-up QMetaMethod for the newConnection(int) slot of the Client. This is faster than using QMetaObject::invokeMethod() as it has to look things up every time.
A timer running in the main thread sets off a signal-slot chain by deleting the first sender. Each senders' deletion triggers the deletion of the next one. Eventually, the last sender sets off the quit() slot in the thread pool. The latter emits the finished() signal when all threads are indeed finished.
#include <QtCore/QCoreApplication>
#include <QtNetwork/QTcpServer>
#include <QtNetwork/QTcpSocket>
#include <QtCore/QQueue>
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <QtCore/QMetaMethod>
// Processes data on a socket connection
class Client : public QObject
{
Q_OBJECT
public:
Client(QObject* parent = 0) : QObject(parent), socket(new QTcpSocket(this))
{
connect(socket, SIGNAL(readyRead()), SLOT(newData()));
connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
SLOT(newState(QAbstractSocket::SocketState)));
qDebug("Client()");
}
~Client() { qDebug("~Client()"); }
signals:
void done();
public slots:
void newConnection(int descriptor) {
socket->setSocketDescriptor(descriptor);
}
private slots:
void newData() {
QByteArray data = socket->readAll();
if (0) qDebug("got %d bytes", data.size());
if (0) qDebug("got a string %s", data.constData());
// here we can process the data
}
void newState(QAbstractSocket::SocketState state) {
qDebug("client new state %d", state);
if (state == QAbstractSocket::UnconnectedState) { emit done(); }
}
protected:
QTcpSocket* socket;
int descriptor;
};
// Connects to a client and sends data to it
class Sender : public QObject
{
Q_OBJECT
public:
Sender(const QString & address, quint16 port, QObject * parent = 0) :
QObject(parent), socket(new QTcpSocket(this)),
bytesInFlight(0), maxBytesInFlight(65536*8)
{
connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
SLOT(newState(QAbstractSocket::SocketState)));
connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(sentData(qint64)));
socket->connectToHost(address, port);
qDebug("Sender()");
}
~Sender() { qDebug("~Sender()"); }
protected:
// sends enough data to keep a maximum number of bytes in flight
void sendData() {
qint64 n = maxBytesInFlight - bytesInFlight;
if (n <= 0) return;
bytesInFlight += n;
socket->write(QByteArray(n, 44)); // 44 is the answer, after all
}
protected slots:
void sentData(qint64 n) {
bytesInFlight -= n;
Q_ASSERT(bytesInFlight >= 0);
sendData();
}
void newState(QAbstractSocket::SocketState state) {
qDebug("sender new state %d", state);
if (state == QAbstractSocket::ConnectedState) sendData();
}
protected:
QTcpSocket* socket;
qint64 bytesInFlight;
qint64 maxBytesInFlight;
};
// Keeps track of threads and client objects
class ThreadPool : public QTcpServer
{
Q_OBJECT
public:
ThreadPool(QObject* parent = 0) : QTcpServer(parent), nextThread(0) {
for (int i=0; i < QThread::idealThreadCount(); ++i) {
QThread * thread = new QThread(this);
connect(thread, SIGNAL(finished()), SLOT(threadDone()));
thread->start();
threads << thread;
}
const QMetaObject & mo = Client::staticMetaObject;
int idx = mo.indexOfMethod("newConnection(int)");
Q_ASSERT(idx>=0);
method = mo.method(idx);
}
void poolObject(QObject* obj) const {
if (nextThread >= threads.count()) nextThread = 0;
QThread* thread = threads.at(nextThread);
obj->moveToThread(thread);
}
protected:
void incomingConnection(int descriptor) {
Client * client;
if (threads.isEmpty()) return;
if (! clients.isEmpty()) {
client = clients.dequeue();
} else {
client = new Client();
connect(client, SIGNAL(done()), SLOT(clientDone()));
}
poolObject(client);
method.invoke(client, Q_ARG(int, descriptor));
}
signals:
void finished();
public slots:
void quit() {
foreach (QThread * thread, threads) { thread->quit(); }
}
private slots:
void clientDone() {
clients.removeAll(qobject_cast<Client*>(sender()));
}
void threadDone() {
QThread * thread = qobject_cast<QThread*>(sender());
if (threads.removeAll(thread)) delete thread;
if (threads.isEmpty()) emit finished();
}
private:
QList<QThread*> threads;
QQueue<Client*> clients;
QMetaMethod method;
mutable int nextThread;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ThreadPool server;
if (!server.listen(QHostAddress::Any, 1101)) qCritical("cannot establish a listening server");
const int senderCount = 10;
Sender *prevSender = 0, *firstSender = 0;
for (int i = 0; i < senderCount; ++ i) {
Sender * sender = new Sender("localhost", server.serverPort());
server.poolObject(sender);
if (!firstSender) firstSender = sender;
if (prevSender) sender->connect(prevSender, SIGNAL(destroyed()), SLOT(deleteLater()));
prevSender = sender;
}
QTimer::singleShot(3000, firstSender, SLOT(deleteLater())); // run for 3s
server.connect(prevSender, SIGNAL(destroyed()), SLOT(quit()));
qApp->connect(&server, SIGNAL(finished()), SLOT(quit()));
// Deletion chain: timeout deletes first sender, then subsequent senders are deleted,
// finally the last sender tells the thread pool to quit. Finally, the thread pool
// quits the application.
return a.exec();
}
#include "main.moc"
Given your explanation, you game engine starts up and creates a connection to some port on localhost. Your Qt program is supposed to accept that connection on port 1101, receive some strings, process them, then send them back.
The code is modified to accept the connection on a fixed port number. All of the data processing, including sending the response back, has to be done from the newData() slot. You can also pass that data off to a different thread, if your computations are very complex. By complex I mean tens of thousands of operations like additions and multiplications, or thousands of trig operations.
The Sender class is there just as an example. Your game engine does the sending, of course, so you don't need the Sender class.
I got my old "the wrong way to do it" code to work. I guess this part was where the error was:
//removed
tcpSocket.disconnectFromHost();
tcpSocket.waitForDisconnected();
emit finished();
...
#include <stdlib.h>
#include <QtNetwork>
#include "MeshServer.hh"
#include <iostream>
#include "TableView.hh"
using namespace std;
FortuneServer::FortuneServer(QObject *parent)
: QTcpServer(parent)
{
}
void FortuneServer::procesServerString(string serverString){
emit procesServerStringToParent(serverString);
}
void FortuneServer::getStringToThread(string serverString){
emit getStringToThreadSignal(serverString);
}
void FortuneServer::incomingConnection(int socketDescriptor)
{
FortuneThread *serverthread = new FortuneThread(socketDescriptor, this);
//connect(&serverthread, SIGNAL(finished()), &serverthread, SLOT(deleteLater()));
QThread* thread = new QThread;
serverthread->moveToThread(thread);
connect(serverthread, SIGNAL(fromThreadString(string)), this, SLOT(procesServerString(string)));
connect(this, SIGNAL(getStringToThreadSignal(string)), serverthread, SLOT(sendString(string)));
connect(serverthread, SIGNAL(finished()), thread, SLOT(quit()));
connect(serverthread, SIGNAL(finished()), serverthread, SLOT(deleteLater()));
connect(serverthread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
FortuneThread::FortuneThread(int socketDescriptor, QObject *parent): QObject(parent), socketDescriptor(socketDescriptor)
{
if (!tcpSocket.setSocketDescriptor(socketDescriptor)) {
emit error(tcpSocket.error());
cout<<"socket error"<<endl;
emit finished();
return;
}
connect(&tcpSocket, SIGNAL(readyRead()), this, SLOT(getString()));
//connect(&tcpSocket, SIGNAL(disconnected()), this, SLOT(ondisconnected()));
}
void FortuneThread::getString()
{
int joj = tcpSocket.bytesAvailable();
if(joj==0){
tcpSocket.disconnectFromHost();
emit finished();
return;
}
char inbuffer[1024];
int buffer_len = 1024;
memset(inbuffer, '\0', buffer_len);
tcpSocket.read(inbuffer,1024);
string instring;
instring = inbuffer;
instring.resize(joj);
emit fromThreadString(instring);
}
void FortuneThread::sendString(string sendoutString)
{
char buffer2[1024];
int buffer_len = 1024;
memset(buffer2, '\0', buffer_len);
strcat(buffer2,sendoutString.c_str());
tcpSocket.write(buffer2,buffer_len);
}
void FortuneThread::ondisconnected()
{
emit finished();
}