Qt - socket és thread

 ( landras | 2008. július 8., kedd - 9:51 )

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ás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Senkinek nincs javaslata?

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...

Ezt nem tudom, hogy hogyan lehetne megoldani. Minden klienssel a kommunikáció külön szálon folyik. Ez nincs több osztályra bontva, de több szál is lehet. Ezen szálak szülőjében pedig van a QTcpServer-t tartalmazó osztály.

no mostmár kezdem kapizsgálni a problémát, de még nem teljesen tiszta... honnan is hívod meg a send metódust? másik szálból?
(ha másik szálból akkor inkább signal/slottal old meg közvetlen meghívás helyett... akkor szokott hasonló hibaüzit dobni...)

A ServerThread send metódusát abból a szálból hívom meg, melyben a QTcpServer van.
Átírom signal/slot -ra.

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.

esetleg próbáld meg ezt:

connect( tcpSocket, SIGNAL( readyRead() ),
                  this, SLOT( recv() ),
                  Qt::DirectConnection );

Forrás:
http://lists.trolltech.com/qt4-preview-feedback/2005-05/thread00739-0.html

Ezt már próbáltam, de sajnos nem oldja meg a problémát. Így sem minden esetben fogad a szerver adatot.

"ServerThread::ServerThread(int socketDescriptor, QObject *parent)
: QThread(parent), socketDescriptor(socketDescriptor)"

Csak kotozkodes: szerintem az int valtozo nevet ird at, nem olyan jo, ha valami osztalynak meg valami valtozonak is ugyanaz a neve, akkor se, ha amugy leforog.

Mindig figyelek arra, hogy ne használjak azonos neveket, de ezt egy példából vettem és elkerülte a figyelmem.
Javítva. :)

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

Visszaírtam mert érdekelt a warning, de nem kaptam sehol.
Egyébként a Qt doksiban is így szerepel: http://doc.trolltech.com/4.3/network-threadedfortuneserver-fortunethread-cpp.html

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!

Valószínűleg véletlenül... (Esetleg nem debugban fordítottad) Rakd csak be abból baj elvileg nem lehet...

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

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?

Fel tudsz rakni valahova egy minimal forrást? (pl pastebin)

Megpróbálok majd ránézni...

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