Qt - socket és thread

Fórumok

Sziasztok!

Qt (4.3.4) segítségével írok egy kliens-szerver alkalmazást.
Két problémám merült fel, melyben a segítségeteket szeretném kérni.
Már több fórumon is átolvastam az erre vonatkozó témákat, de nem jutottam sokkal előbbre.

1. A klienstől érkező adatokat a szerver nem mindig fogadja.
A kliens sikeresen csatlakozik a szerverhez. Van amikor a szerver sikeresen fogadja az adatot, de legtöbbször nem. Wiresharkkal megnéztem, hogy mi történik, a kliens minden esetben elküldi az adatot.
Több kliens egy időben való párhuzamos kiszolgálása érdekében QThread-eket használok. Ha egy kliens csatlakozik, akkor létrejön neki egy thread, amely run metódusa a következő:


void ServerThread::run()
{
	tcpSocket = new QTcpSocket();
	if (!tcpSocket->setSocketDescriptor(socketDescriptor))
	{
		emit error(tcpSocket->error());
		return;
	}
	connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(recv()));
	exec();
}

Amikor a fogadás nem történik meg, akkor a tcpSocket nem is küld readyRead() signalt, tehát nem a recv() fv-ben van a hiba.
Valakinek van tippje, hogy mi lehet a probléma oka? Arra sem sikerült rájönnöm, hogy mitől függ, hogy a server fogad adatot vagy nem.

2. A másik problémámat pedig a szálak okozzák.
A ServerThread osztálynak van egy send() metódusa, amelyben a tcpSocket write() metódusát hívom meg (a tcpSocket objektum a run()-ban lett létrehozva). Ekkor a következő üzenetet kapom: "QObject: Cannot create children for a parent that is in a different thread. (Parent is QNativeSocketEngine(0x8093530), parent's thread is ServerThread(0x808a280), current thread is QThread(0x8052740)"
Ez a probléma akkor van, ha a szerver küld a kliensnek. Erre mit tudtok javasolni? Azt nem tudtam megoldani, hogy egy szálon legyenek, mert GUI-s programról van szó, így valahol mindenképpen meg kell hívni egy másik szálbeli metódust.

Előre is köszönöm a válaszokat!

Hozzászólások

Részletesebb, de még minimális forrás?

Fortune client/server megy nálad rendesen? Ahhoz képest mi van nálad máshogy?

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

hi

elkezdett érdekelni a többszálas problémád, de nem egészen értem melyik socketed melyik szálban van, tudnál a kódbol beilleszteni még?

Amúgy a javaslatom, hogy az összes socket-es mókát rakd egy osztályba és akkor megkerülöd ezt a szálas problémát...

A Fortune client/server tökéletesen működik. Abban minden egyes adatkérés után a kliens és a szerver közti kapcsolat megszűnik, nálam állandó kapcsolat van a kettő között. Valamint a Fortune server csak küldést végez, fogadást nem, mindezt a run()-ban.

Kliens fogadását végző kódrészlet:


ServerThread *thread = new ServerThread(tcpServer->nextPendingConnection()->socketDescriptor(), this);
// ...
thread->start();

Ezzel nincs probléma.

A ServerThread osztály:
-----------------------

ServerThread.h:


#ifndef SERVERTHREAD_H
#define SERVERTHREAD_H

#include <QThread>
#include <QTcpSocket>
#include <QDataStream>

class ServerThread : public QThread
{
	Q_OBJECT
	public:
		ServerThread(int socketDescriptor, QObject *parent=0);
		void run();
		void send(QString str);
	public slots:
		void recv();
		void sendToClient(QString, ServerThread*);
	signals:
		void error(QTcpSocket::SocketError socketError);
		void newMsg(QString, ServerThread*);
	private:
		QTcpSocket *tcpSocket;
		int socketDescriptor;
		QString msg;
		quint16 blockSize;
};

#endif

ServerThread.cpp:


#include "ServerThread.h"
#include <QMessageBox>

ServerThread::ServerThread(int socketDescriptor, QObject *parent)
	: QThread(parent), socketDescriptor(socketDescriptor)
{
	blockSize = 0;
}

void ServerThread::run()
{
	tcpSocket = new QTcpSocket();
	if (!tcpSocket->setSocketDescriptor(socketDescriptor))
	{
		emit error(tcpSocket->error());
		return;
	}
	connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(recv()));
	exec();
}

void ServerThread::sendToClient(QString str, ServerThread* sender)
{
	if(sender != this)
		send(str);
}

void ServerThread::send(QString str)
{
	QByteArray block;
	QDataStream out(&block, QIODevice::WriteOnly);
	out.setVersion(QDataStream::Qt_4_0);
	out << (quint16)0;
	out << str;
	out.device()->seek(0);
	out << (quint16)(block.size() - sizeof(quint16));

	tcpSocket->write(block); // !!!
}


void ServerThread::recv()
{
	QDataStream in(tcpSocket);
	in.setVersion(QDataStream::Qt_4_0);

	if (blockSize == 0)
	{
		if (tcpSocket->bytesAvailable() < (int)sizeof(quint16))
			return;
		in >> blockSize;
	}

	if (tcpSocket->bytesAvailable() < blockSize)
		return;

	in >> msg;
	blockSize = 0;
	emit newMsg(msg, this);

}

Azt gondolom, hogy ebben lehet a hiba, mert mint írtam a kliens minden esetben elküldi az adatot, de a tcpSocket nem mindig küld readyRead signalt.

Ollalaaa, azért itt nagyobb a gond. Nem az a probléma, hogy hasonló neve van egy osztálynak és egy változónak (engem pl ez nem szokott zavarni, sőt... (Az osztálynevek mindig nagy, a változók mindig kisbetűvel kezdődnek, összekeverés kizárva)), hanem ugyanaz a neve a konstruktor paraméterének, mint a változónak. Most mi is ad minek értéket?
Minimum egy kövér warning van valahol...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Elég nagy szégyen... :)

Én régebben tuti szívtam emiatt, de azóta lehet, hogy javult a gcc... De jobb az ilyet kerülni...
Valószínűleg csak konstruktorban az inicializáló listában megengedett...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Miért socketDescriptort adsz át, miért nem a QTcpSocket mutatót?

Mert mi is történik?


ServerThread *thread = new ServerThread(tcpServer->nextPendingConnection()->socketDescriptor(), this);

"tcpServer->nextPendingConnection()"
Létrehoz egy QTcpSocket objectet, aminek lekéred a socket descriptorját, ezt továbbadod.
Viszont ezzel a QTcpSocket-tel nem csinálsz semmit, ami alapértelmezés szerint addig él amíg a QTcpServer...

Tehát továbbadtad egy másik thread-nek a socket descriptort, ami majd megpróbál belőle QTcpSocket-et csinálni, a setDescriptor()-ral...
Nade mit ír a doksi: "Note: It is not possible to initialize two abstract sockets with the same native socket descriptor."
Viszont még él az előző QTcpSocket object...

Én csak azon csodálkozom, hogy egyáltalán van, hogy működik...

Két dolgot tehetsz:
QTcpSocket mutatóját adod át a threadnek (valószínűleg kell majd egy QObject::moveToThread hívás is).
Socket descriptort adsz át, de nem így szerzed meg, hanem a újraimplementálod a QTcpServer::incomingConnection fv-ét. lásd itt

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

És valóban!
A threadnek a tcpSocket mutatóját adtam át, és így tökéletesen működik. Olyannyira, hogy ettől a 2. probléma is megoldódott.
Nagyon köszönöm!

Azt viszont nem teljesen értem, hogy moveToThread nélkül miért működik, ha egy másik szálnak adom át.

Még egyszer köszönöm!

Nalam egy nagyon hasonlo helyzet all fenn, mint ami a tema indito volt, illetve pontosabban csak a 2. pontban leirt tunetek fordulnak elo. Mindamellett, hogy (latszolag) tokeletesen mukodik egy hasonlo modon megvalositott komunnikacio, "warning"-kent futasi idoben a socketre torteno iras kozben a kovetkezo hiba jon elo:

"QObject: Cannot create children for a parent that is in a different thread. (Parent is NativeSocketEngine(0x94e88f0), parent's thread is ClientThread(0x94e8140), current thread is QThread(0x94cc390)"

A problema en alapbol a te megoldasi javaslatoddal dolgoztam, megsem jo.
("Socket descriptort adsz át, de nem így szerzed meg, hanem a újraimplementálod a QTcpServer::incomingConnection fv-ét.")

Valami egyeb otlet?