PDA

View Full Version : Problem encountered in http server implementation



ada10
19th August 2010, 10:04
I am writing a simple multithreaded Http server using QTcpServer. The server spawns a new thread for every client connection. The new thread is being created in a slot connected to the readyRead() signal.



void serverwindow::AcceptClientConn()
{
ui->textEdit->append("\nNew client connection received");

clientConnection = tcpServer->nextPendingConnection();

ui->textEdit->append("\nNew client connection socketobtained");

connect(clientConnection, SIGNAL(disconnected()),
clientConnection, SLOT(deleteLater()));

connect( clientConnection, SIGNAL(readyRead()),
this, SLOT(readClient()) );
}

void serverwindow::readClient()
{
//read the data obtained from client

ui->textEdit->append("\nreadClient");
QTcpSocket* clientSocket = (QTcpSocket*)sender();

//create new thread to handle the client request
clienthandler* clientThread = new clienthandler( clientSocket );

connect( clientThread,SIGNAL(finished()),
clientThread,SLOT(deleteLater()) );
clientThread->start();
clientThread->setPriority(QThread::HighestPriority);

}


The clienthandle is a subclass of QThread. Its implementation of run() method is as below -


void clienthandler::run()
{
clientConnSocket->moveToThread(QThread::currentThread());
if(clientConnSocket->canReadLine())
{
QString curData(clientConnSocket->readLine());
QStringList tokens = curData.split(QRegExp("[ \r\n][ \r\n]*"));
if ( tokens[0] == "GET" )
{
//try to send a file's contents

//1. Small sized html file
QFile htmlfile("c:\\server_files\\index.html");

if (!htmlfile.open(QIODevice::ReadOnly))
return;

QString content_type = "video/mp4;";
QTextStream os( clientConnSocket );
//os.setAutoDetectUnicode(true);
os << "HTTP/1.0 200 Ok\r\n"
"Content-Type: "<< content_type <<"charset=\"utf-8\"\r\n"
"\r\n";
os.flush();

// Streaming the file
QByteArray block = htmlfile.readAll();
clientConnSocket->write(block);
}
}
clientConnSocket->disconnectFromHost();
clientConnSocket->close();
}



When i run the server, it receives client connections but issues the following error -

QObject::moveToThread: Cannot move objects with a parent
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QNativeSocketEngine(0x9640878), parent's thread is QThread(0x3d59e8), current thread is clienthandler(0x9644cb0)
QSocketNotifier: socket notifiers cannot be disabled from another thread


What changes should i make in the code to avoid these errors ? I know its the problem with the socket reference being passed to the new thread , but how else can I do it ? I know this question has been asked in this forum a lot many times, but I could not find a suitable answer.

tbscope
19th August 2010, 11:02
What changes should i make in the code to avoid these errors ?
Don't use threads. Yes, I'm serious.


I know its the problem with the socket reference being passed to the new thread , but how else can I do it ?
The problem with threads is that you really need to know which code is being run inside which thread.
Your code mixes the code beyond any computer being capable of understanding it :-) Just kidding, but the structure isn't clean or clear.

If I would use threads for a server I would create a socket object, just a simple QObject that you use like any other object. You can connect signals and slots like you do with any normal object.
Then, you move this object to a new thread.



class MySuperHTTPSocket : public QTcpSocket
{
Q_OBJECT

public:
...

public slots:
void mySlot1();

};



void MySuperHTTPServer::handleIncommingConnection(...)
{
MySuperHTTPSocket *socket = new MySuperHTTPSocket;

connect(socket, SIGNAL, this, SLOT);
connect(this, SIGNAL, socket, SLOT);

QThread *socketThread = new QThread;

socket->moveToThread(socketThread);
socketThread->start();
}


Or something like that.

ada10
19th August 2010, 11:54
Okay, I tried something similar to the Threaded Fortune server example. The end result of the code was this -


hostingserver.h ( subclass of QTcpserver )


class hostingserver : public QTcpServer {
Q_OBJECT
public:
hostingserver(QObject *parent = 0);
~hostingserver();

protected:
void incomingConnection(int socketDescriptor);

private:

};

hostingserver.cpp -


hostingserver::hostingserver(QObject *parent) :
QTcpServer(parent)
{

}

hostingserver::~hostingserver()
{

}

void hostingserver::incomingConnection(int socketDescriptor)
{

clienthandler *thread = new clienthandler(socketDescriptor,this);
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}

the clienthandler is a QThread -


class clienthandler : public QThread
{
Q_OBJECT
public:
clienthandler(int socketDescriptor, QObject *parent);

~clienthandler();

public: //implemented from QRunnable
void run();

signals:
void error(QTcpSocket::SocketError socketError);

private:
int socketDescriptor;

};



clienthandler implementation -


clienthandler::clienthandler(
int aSocketDescriptor,QObject *parent) :
QThread(parent),socketDescriptor(aSocketDescriptor )
{

}

clienthandler::~clienthandler()
{

}

void clienthandler::run()
{
QFile fn("c:\\server_files\\server_log.txt");
fn.open(QIODevice::WriteOnly | QIODevice::Text);
fn.write(QString("run impl called").toAscii());
QTcpSocket clientConnSocket;

if (!clientConnSocket.setSocketDescriptor(socketDescr iptor)) {
emit error(clientConnSocket.error());
return;
}

fn.write(QString("\nsetSocketDescriptor called").toAscii());

bool isCorrect = clientConnSocket.canReadLine();
if( !isCorrect )
fn.write(QString("\nLine cannot be read").toAscii());
if(clientConnSocket.canReadLine())
{
QString curData(clientConnSocket.readLine());
QStringList tokens = curData.split(QRegExp("[ \r\n][ \r\n]*"));
if ( tokens[0] == "GET" )
{
//try to send a file's contents
fn.write(QString("GET obtained").toAscii());
//1. Small sized html file
QFile htmlfile("c:\\server_files\\index.html");

if (!htmlfile.open(QIODevice::ReadOnly))
return;

QString content_type = "text/html;";
QTextStream os( &clientConnSocket );
//os.setAutoDetectUnicode(true);
os << "HTTP/1.0 200 Ok\r\n"
"Content-Type: "
<< content_type
<<"charset=\"utf-8\"\r\n"
<<"\r\n";
os.flush();

// Streaming the file
QByteArray block = htmlfile.readAll();
clientConnSocket.write(block);
}
}
clientConnSocket.disconnectFromHost();
fn.close();
}

clientConnSocket.canReadLine() always returns false in the above code. Whta should I do for this ?

tbscope
19th August 2010, 12:00
clientConnSocket.canReadLine() always returns false in the above code. Whta should I do for this ?

You try to read from the socket right after sending something.
There's no time to receive anything yet. You need to wait.

ada10
19th August 2010, 12:13
I am testing this server using a Firefox and the server running on the same machine. What is the appropriate should the server wait for the request to arrive ?

tbscope
19th August 2010, 12:35
What is the appropriate should the server wait for the request to arrive ?

That's the wrong question to ask, and here's why:
You can never know.

You can set a time out though to, for example, 10 seconds.

Here are the two techniques to deal with the data:
1. Asynchronous, or non-blocking. This is event driven. Every time data becomes available, the socket will post an event resulting in a signal (the readyRead signal)
If you connect a slot to this signal, you can use readLine, or any of the other read functions. In the mean time, the socket will do nothing except checking the buffers and it will not block the event queue.

2. Synchronous, or blocking. This is not event driven. The socket just waits for data te become available. It blocks the event queue and can almost only be used in threads.
Use the waitFor...() functions.

Suggestion: Do not use threads and certainly not the blocking functions unless you absolutely need to.

ada10
19th August 2010, 12:46
I used both the methods as you mentioned. In the asynchronous method I tried, the code looked like this -


clientConnection = tcpServer->nextPendingConnection();

connect(clientConnection, SIGNAL(disconnected()),
clientConnection, SLOT(deleteLater()));

connect( clientConnection, SIGNAL(readyRead()),
this, SLOT(readClient()) );

where the readClient() slot implementation is as shown -


void serverwindow::readClient()
{
QTcpSocket* clientSocket = (QTcpSocket*)sender();

//pass this socket to a new thread which send some data to client
}

Here as I obtain some data on socket, I used to spawn a new thread with the socket reference as a parameter to the thread. This caused huge problems because that socket reference could not be "moved" to another thread. So this was the issue I faced with the asynchronous method.

So I used th synchronous method with waitForReadyRead() method with some parameter ( which is not the way code should be written; I understand ).
Certainly I know, the asynch method is better, but I could not find a solution to the above problem . If you could provide some solution to this, I would be happy to incorporate into my code.

tbscope
19th August 2010, 12:49
I'll try to write an example later today.