Russian Qt Forum
Ноябрь 22, 2024, 23:36 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3 ... 5   Вниз
  Печать  
Автор Тема: [РЕШЕНО] Простой многопоточный сервер  (Прочитано 32956 раз)
Reklats
Гость
« : Декабрь 08, 2011, 01:27 »

    Здравствуйте.
    Скоро будет неделя, как бьюсь над этой проблемой. Задача не тривиальна: создать сервер и каждое новое соединение обрабатывать в новом потоке. Проблема как раз с теми самыми потоками. Перелопатил кучу форумов (раз, два), примеры (threadedfortuneserver), но ничего подходящего не нашёл. Я только начинаю использовать потоки и сложные изъяснения естественно не понимаю.
    Сам сервер взял из книги Шлее "Qt4.5. Профессиональное программирование на C++". Многопоточность стал прикручивать из примера "threadedfortuneserver". Сервер соединения принимает, данные принимает \ отправляет, но при закрытии клиента, сервер кидает исключение и закрывается, да и потоки толком не работают. Я уже и не знаю, что делать.
    Предполагаю, что нужно использовать QObject::moveToThread, но вот где...
    Вот код основных мест:

QServerThread - класс потока, в котором обрабатывается соединение.
Код
C++ (Qt)
#ifndef QSERVERTHREAD_H
#define QSERVERTHREAD_H
#define NUMTOSTR(x) QString::number(x)
 
#include <QThread>
#include <QTcpSocket>
 
class QServerThread : public QThread
{
   Q_OBJECT
 
public:
   explicit QServerThread(int socketDescriptor, QObject *parent);
   ~QServerThread();
   void run();
 
private:
   int socketDescriptor;
   qint16 m_nNextBlockSize;
   QTcpSocket pClientSocket;
   QThread *parentThread;
   void sendToClient(const QString &nstr);
 
private slots:
   void slotReadFromClient();
   void slotDisconnect();
 
signals:
   void error(QTcpSocket::SocketError socketError);
   void print(const QString &info);
};
 
#endif // QSERVERTHREAD_H
 
//===============================
 
#define PRINT(x) emit QServerThread::print((QString)x);
 
QServerThread::QServerThread(int socketDescriptor, QObject *parent)
   : QThread(parent), socketDescriptor(socketDescriptor), parentThread(parent->thread())
{
   this->moveToThread(this);
}
 
QServerThread::~QServerThread(){
   delete parentThread;
}
 
void QServerThread::run(){
 
   if (!pClientSocket.setSocketDescriptor(socketDescriptor)) {
       emit error(pClientSocket.error());
       return;
   }
 
   //pClientSocket.moveToThread(this);
   PRINT("New connection from "+
         pClientSocket.peerAddress().toString() + ":" +
         NUMTOSTR(pClientSocket.peerPort()))
 
   sendToClient("HELLO WORLD");
 
   connect(&pClientSocket, SIGNAL(readyRead()), this, SLOT(slotReadFromClient()));
   connect(&pClientSocket, SIGNAL(disconnected()), this, SLOT(slotDisconnect()));
   connect(&pClientSocket, SIGNAL(destroyed()), this, SLOT(quit()));
 
   exec();
   this->moveToThread(parentThread);
}
 

QServerBase - класс сервера
Код
C++ (Qt)
class QServerBase : public QTcpServer
{
   Q_OBJECT
public:
   explicit QServerBase(QObject *parent = 0);
 
protected:
   void incomingConnection(int socketDescriptor);
 
signals:
   void log(const QString &info);
 
};
 
//========================
 
QServerBase::QServerBase(QObject *parent) :
   QTcpServer(parent)
{
}
 
void QServerBase::incomingConnection(int socketDescriptor){
   QServerThread *thread = new QServerThread(socketDescriptor, this);
   //thread->moveToThread(thread); in constructor
   connect(thread, SIGNAL(print(QString)), this, SIGNAL(log(QString)));
   connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
   thread->start();
}
 
 

Вывод консоли
Код
C++ (Qt)
QObject::moveToThread: Cannot move objects with a parent // on connect client
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x1412000), parents thread is QThread(0x627530), current thread is QServerThread(0x1411ff0)
Error - RtlWerpReportException failed with status code :-1073741823. Will try to launch the process directly // on disconnect client
 

UPD FIN: Для тех, кто не хочет искать решение по всей теме, вот готовые рабочие сорцы многопоточного сервера.
« Последнее редактирование: Декабрь 13, 2011, 20:21 от Reklats » Записан
Bepec
Гость
« Ответ #1 : Декабрь 08, 2011, 07:20 »

Эм, можно вопрос?
А зачем вам exec(); в потоке ?

Список замечаний (пусть и невысокого я уровня, но насоветую):

1) - moveToThread используется, когда из основного(или другого) потока, вызываются открытые функции Вашего рабочего потока. (в других случаях он не нужен)
2) - moveToThread должен быть в 1 месте. В конструкторе потока. Зачем вы его вызываете в методе run, я не могу представить.
3) + moveToThread в конструкторе потока, у вас расположен верно Улыбающийся
4) - Чтобы поток запустился и работал событийно (т.е. чтобы откликался на события), то нужно вызвать метод QThread::run();(в методе run потока)  (и exec тут не нужен)

К сожалению вы привели не весь код нитки(нету методов диссконекта и чтения), потому ничего более не могу посоветовать.



Записан
andrew.k
Гость
« Ответ #2 : Декабрь 08, 2011, 09:26 »

После того, как run отработал moveToThread "обратно" делать не нужно, т.к. это ни на что не повлияет.
Сылку на parentThread можешь убрать, она тебе не нужна.

А крах во почему.
Код
C++ (Qt)
QServerThread::~QServerThread(){
   delete parentThread;
}
Зачем ты в деструкторе удаляешь родителя? Это антикутишный подход)

UPD это еще и не родитель, а указатель на поток родителя.
(вот родитель удивится Улыбающийся
« Последнее редактирование: Декабрь 08, 2011, 09:47 от andrew.k » Записан
andrew.k
Гость
« Ответ #3 : Декабрь 08, 2011, 09:41 »

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x1412000), parents thread is QThread(0x627530), current thread is QServerThread(0x1411ff0)
Создавай сокет динамически внутри run и не придется ничего двигать.
Либо добавь pClientSocket.moveToThread(this);

Вообще по-моему лучший подход, это создавать все необходимые тебе в потоке классы, непосредственно в самом потоке (в run)
« Последнее редактирование: Декабрь 08, 2011, 09:42 от andrew.k » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #4 : Декабрь 08, 2011, 10:33 »

    Скоро будет неделя, как бьюсь над этой проблемой. Задача не тривиальна: создать сервер и каждое новое соединение обрабатывать в новом потоке.
Неделя еще не срок. Подход как раз тривиальный/банальный, "новое соединение - новый поток" именно это первое что приходит в голову. Но это не профессионально  Улыбающийся
Записан
Reklats
Гость
« Ответ #5 : Декабрь 08, 2011, 15:17 »

    Проблема всё ещё имеет место быть, хотя и частично исчезла. Сделал динамический сокет (собственно как и советовал Шлее в своей книге, а я его советом пренебрёг) и убрал все упоминания о родительском потоке parentThread. Теперь при закрытии клиента сервер не падает, а любезно информирует об отключении клиента. А вот уже при закрытии сервера кидается исключение - RtlWerpReportException и тут, я думаю, дело в потоках. К тому же this->moveToThread(this); (в конструкторе потока) не отрабатывает - пишет в консоль, что нельзя переместить объект с родителем  В замешательстве. И пресловутое Cannot create children... осталось. Я всё ещё делаю что-то не так.

    Скоро будет неделя, как бьюсь над этой проблемой. Задача не тривиальна: создать сервер и каждое новое соединение обрабатывать в новом потоке.
Неделя еще не срок. Подход как раз тривиальный/банальный, "новое соединение - новый поток" именно это первое что приходит в голову. Но это не профессионально  Улыбающийся
Я в курсе, что это не профессионально, но если небольшое количество потоков - можно. По уму надо делать пул потоков, хотя количество потоков и обусловлено производительностью машин, но да, не каждая машина потянет 1000 активных соединений-потоков. У меня и эта банальщина пока не работает  Смеющийся.

Эм, можно вопрос?
А зачем вам exec(); в потоке ?
Для того, чтобы обрабатывать соединение - принимать \ отправлять данные. Без него (exec();) соединение закрывается сразу после коннекта ("привет" только отправляет клиенту и всё).

    И ещё. Когда я шлю серверу первую порцию данных, он пишет что принято [любое число] байт и выводит пустую строку. Все следующие данные принимает нормально. Тут я тоже в замешательстве.

Свежая порция исходников.
QServerThread, кроме него ничего не менял.
Код
C++ (Qt)
class QServerThread : public QThread
{
   Q_OBJECT
 
public:
   explicit QServerThread(int socketDescriptor, QObject *parent);
   void run();
 
private:
   int socketDescriptor;
   qint16 m_nNextBlockSize;
   //QTcpSocket *pClientSocket;
   void sendToClient(QTcpSocket *pClientSocket, const QString &nstr);
 
private slots:
   void slotReadFromClient();
   void slotDisconnect();
 
signals:
   void error(QTcpSocket::SocketError socketError);
   void print(const QString &info);
};
//====================================
QServerThread::QServerThread(int socketDescriptor, QObject *parent)
   : QThread(parent), socketDescriptor(socketDescriptor)
{
   this->moveToThread(this); // this line doesn`t work.
}
 
void QServerThread::run(){
 
   QTcpSocket *pClientSocket = new QTcpSocket(this);
 
   if (!pClientSocket->setSocketDescriptor(socketDescriptor)) {
       emit error(pClientSocket->error());
       return;
   }
 
   PRINT("New connection from "+
         pClientSocket->peerAddress().toString() + ":" +
         NUMTOSTR(pClientSocket->peerPort()))
 
   sendToClient(pClientSocket,"HELLO WORLD");
 
   connect(pClientSocket, SIGNAL(readyRead()), this, SLOT(slotReadFromClient()));
   connect(pClientSocket, SIGNAL(disconnected()), this, SLOT(slotDisconnect()));
   connect(pClientSocket, SIGNAL(destroyed()), this, SLOT(quit()));
 
   exec();
}
 
void QServerThread::sendToClient(QTcpSocket *pClientSocket, const QString &nstr){
   QByteArray dataBlock;
   QDataStream out(&dataBlock, QIODevice::WriteOnly);
   out.setVersion(QDataStream::Qt_4_0);
   out << qint16(0) << QTime::currentTime() << nstr;
   out.device()->seek(0);
   out << qint16(dataBlock.size() - sizeof(qint16));
 
   pClientSocket->write(dataBlock);
   PRINT("Sending data to "+
         pClientSocket->peerAddress().toString() + ":" + NUMTOSTR(pClientSocket->peerPort()))
}
 
void QServerThread::slotReadFromClient(){
   QTcpSocket *pClientSocket = (QTcpSocket*)sender();
 
   QDataStream in(pClientSocket);
   in.setVersion(QDataStream::Qt_4_0);
 
   for(;;){
       if(!m_nNextBlockSize){
           if (pClientSocket->bytesAvailable() < (int)sizeof(qint16)){
               break;
           }
           in >> m_nNextBlockSize;
       }
 
       if (pClientSocket->bytesAvailable() < m_nNextBlockSize){
           break;
       }
 
       QString data;
       QTime time;
       in >> time >> data;
 
       PRINT(pClientSocket->peerAddress().toString() + ":" + NUMTOSTR(pClientSocket->peerPort())+" has send " + NUMTOSTR(m_nNextBlockSize)+"b > " + data)
       m_nNextBlockSize = 0;
 
       sendToClient(pClientSocket, "Server Response > Received \"" + data + "\"");
   }
}
 
void QServerThread::slotDisconnect(){
   QTcpSocket *pClientSocket = (QTcpSocket*)sender();
   PRINT(pClientSocket->peerAddress().toString() + ":" + NUMTOSTR(pClientSocket->peerPort())+" is disconnected")
   pClientSocket->deleteLater();
}
 

Консоль
Код
C++ (Qt)
QObject::moveToThread: Cannot move objects with a parent // on new connection
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QServerThread(0x6dc038), parents thread is QThread(0x817530), current thread is QServerThread(0x6dc038)
Error - RtlWerpReportException failed with status code :-1073741823. Will try to launch the process directly // on close
 
Записан
andrew.k
Гость
« Ответ #6 : Декабрь 08, 2011, 15:47 »

Цитировать
пишет в консоль, что нельзя переместить объект с родителем  . И пресловутое Cannot create children... осталось. Я всё ещё делаю что-то не так.
Очевидно, что ты пытаешься переместить объект с родителем? )

убери вообще moveToThread он не нужен.
Записан
andrew.k
Гость
« Ответ #7 : Декабрь 08, 2011, 15:49 »

Код
C++ (Qt)
QTcpSocket *pClientSocket = new QTcpSocket(this);
this убери.

Код
C++ (Qt)
QTcpSocket *pClientSocket = new QTcpSocket();

А где delete pClientSocket?

Вообще сделал бы на стеке и не парился.
« Последнее редактирование: Декабрь 08, 2011, 15:52 от andrew.k » Записан
Reklats
Гость
« Ответ #8 : Декабрь 08, 2011, 17:17 »

    Спасибо, почти всё стало работать гладко. А вот при закрытии сервера всё равно кидается исключение.
    Вообще, использовать moveToThread меня надоумил вот этот форум. Вначале я писал без  Улыбающийся.
    Непонятно пару моментов:
1)
Код
C++ (Qt)
QTcpSocket *pClientSocket = new QTcpSocket();
Почему тут не надо отправлять указатель на родительский объект (поток)? При создании сервера это же делается.
Код
C++ (Qt)
m_pServerBase = new QServerBase(this);
This в данном случае указывает на главный поток.

2)
А где delete pClientSocket?
А разве это
Код
C++ (Qt)
void QServerThread::slotDisconnect(){
   QTcpSocket *pClientSocket = (QTcpSocket*)sender();
  //some code
   pClientSocket->deleteLater();
}
 
не удаляет сокет? И если нет, то где ставить? Переопределять метод quit()?

3)
Вообще сделал бы на стеке и не парился.
Можно поподробнее про это?
Записан
ufna
Гость
« Ответ #9 : Декабрь 08, 2011, 17:18 »

Вообще сервера на Erlang писать надо  Смеющийся
Записан
andrew.k
Гость
« Ответ #10 : Декабрь 08, 2011, 19:18 »

Я не заметил deleteLater(). тогда ok.

Разница в том, что в 1 случае ты создаешь сокет в потоке, а this указывает на объект в другом потоке, о чем ты и получал предупреждение.

А во 2 случае обычные экземпляр класса и все как обычно.

Посмотри Threaded Fortune Server или какой-то пример. Как там создается сокет. Как раз на стеке.
Записан
Reklats
Гость
« Ответ #11 : Декабрь 08, 2011, 20:27 »

andrew.k, плюсую в карму. Спасибо  Улыбающийся.

    Разобрался с исключениями при закрытии. Это я в главном потоке в деструкторе удалял много лишнего. Теперь многопоточность работает. Осталось разобраться почему первую партию данных сервер не принимает нормально. Хотя все последующие нормально принимает.
    И ещё у меня складывается такое впечатление, что память не освобождается должным образом. Судя по диспетчеру, при закрытии соединения что-то высвобождается, но не всё (прирост 120кб, убыль 20, итого 100кб осталось висеть от одного соединения (!)).

Цитата: andrew.k
Посмотри Threaded Fortune Server или какой-то пример. Как там создается сокет. Как раз на стеке.
    Пересмотрел ещё раз пример. Там в потоке (в методе run() потока) создаётся статически (?) сокет. И отсылка данных идёт в этом же потоке. Сразу при подключении. Я тоже создаю сокет, динамически правда. Но вот заметил одну вещь: коннекчусь я к слотам в дочернем потоке, а вот отрабатывают сами слоты уже в главном. Странно. Я думал, что весь класс - это отдельный поток, а оказывается только метод run().

Записан
andrew.k
Гость
« Ответ #12 : Декабрь 08, 2011, 21:13 »

не статически, а на стеке.

да про слоты я забыл)
создавай свой поток без родителя, а в run добавь movetothread(this)
тогда слоты будут отрабатывать в потоке.
Записан
Reklats
Гость
« Ответ #13 : Декабрь 08, 2011, 22:37 »

Слоты упорно не хотят запускаться в дочернем потоке.
Код
C++ (Qt)
this->moveToThread(this->thread());
Ставил и в констркуторе, и в QServerThread::run();, и с родителем, и без родителя...

Вот что вывела консоль :
Код
C++ (Qt)
QServerBase::QServerBase() 0x145c
QServerThread::QServerThread() 0x145c
QServerThread::QServerThread(QObject *parent) 0x145c  // parent thread id
QThread::run() 0x1504
QServerThread::slotReadFromClient() 0x145c
QServerThread::slotReadFromClient() 0x145c
 

Первый пакет данных по прежнему теряется.
Записан
andrew.k
Гость
« Ответ #14 : Декабрь 08, 2011, 22:42 »

Ты разницу видишь?

movetothread(this)

this->moveToThread(this->thread());

??

Делать это надо в run
Записан
Страниц: [1] 2 3 ... 5   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.571 секунд. Запросов: 22.