Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ Qt Ввод/Вывод, Сеть. Межпроцессное взаимодействие _ QThread, QEvent, QTcpServer

Автор: 512es 10.11.2008, 16:00

всем привет!

пишу многопоточный высокопроизводительный сервер на qt.

сервер прослушивает коннекты на определённый порт и на каждое входящее соединение создаёт отдельный поток.
при каждом пришедшем пакете поток отсылает главному потоку сообщение через customEvent.

вроде работает, только не знаю как правильнее заставить все дочерние потоки (или один выборочно) отослать определённый пакет клиентам?


закинул все потоки массив QVector и передаю в customEvent главного потока ид того кто отправил. правильный путь ли я выбрал?

Автор: Litkevich Yuriy 10.11.2008, 16:27

Цитата(512es @ 10.11.2008, 19:00) *
при каждом пришедшем пакете поток отсылает главному потоку сообщение через customEvent.
может лучше сигналы и слоты использовать, Qt'я их сама в события превратит.

Цитата(512es @ 10.11.2008, 19:00) *
вроде работает, только не знаю как правильнее заставить все дочерние потоки (или один выборочно) отослать определённый пакет клиентам?
а вчем именно загвоздка?

Автор: ViGOur 10.11.2008, 16:48

Если я тебя правильно понял, то тебе нужен менеджер соединений. А раз так, то обычным QVector тебе не обойтись, нужно создать класс для управления и просмотра соединений (сокетов), а потоки мониторить тебе не нужно, так как достаточно повесить событие на закрытие соединения для завершение потока.

Автор: 512es 10.11.2008, 19:05

Litkevich Yuriy, customEvent потому что хочу всё что относится к каждому сокету вывести в поток этого сокета. дабы не вешать главный поток при ддос атаках. а как я понял, система сигнал\слот выполняет код в главном потоке...


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

вообщем то я думаю даже не нужно заталкивать класс потоков в QVector...
надо просто как то присваивать каждому сокету=потоку свой ИД и по этому ИДу иметь возможность послать данные в поток, где открыт сокет. притом так, чтобы они обработались именно в дочернем потоке (где сокет открыт).

вот в том как это правильнее сделать и есть мой вопрос =)

Автор: Litkevich Yuriy 10.11.2008, 19:16

Цитата(512es @ 10.11.2008, 22:05) *
а как я понял, система сигнал\слот выполняет код в главном потоке...
про это не скажу, так как сам еще не пробывал, но вроде слот выполняется там, где его собственный объект.

Автор: ViGOur 11.11.2008, 0:19

Цитата(Litkevich Yuriy @ 10.11.2008, 19:16) *
но вроде слот выполняется там, где его собственный объект.
В том потоке в котором был создан объект получатель.
Цитата(512es @ 10.11.2008, 19:05) *
дабы не вешать главный поток при ддос атаках.
Тогда тебе нужно отказаться от схемы "1 соединени - 1 поток", а подумать о реализации схему "много соединений - 1 поток". Например в винде можно обрабатывать в одном потоке 64 соединения, в никсах не знаю сколько, но думаю, что не меньше...

Да и то это поможет при маленьком DDOS'ике, при серьезном, когда порядка 100 000 соединений к тебе идет у тебя тупо не хватит ресурсов для обработки.

Что-то ты зациклился на ID соединений, чем тебе не нравится указатель на соединение? Что ID 4 байта, что указатель 4 байта, только с указателем нужно меньше телодвижений чем с ID. :)

Список соединений тебе скорее всего понадобится, иначе где класс менеджер соединений будет хранит указатели на соединения?
Класс менеджер соединений должен быть синглтоном, надеюсь не нужно объяснять почему.
При установке соединений сервер создает указатель на него, который помещает в менеджер соединений.
При закрытии соединения посылается сигнал менеджеру соединений для того, чтобы он удалил из списка данное соединение, и если соединение было последним в потоке, то завершать поток (в это случае нужно предусмотреть на нагруженном сервере пул потоков, чтобы не тратить системные ресурсы на создание новых потоков).
И прочее...

Автор: 512es 11.11.2008, 11:53

Цитата(ViGOur)
В том потоке в котором был создан объект получатель

я провёл эксперимент:
прямо в секции run() потока подключаю сигнал readyRead() с сокета, на слот onReadyRead(), который в том же потоке.
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));

в onReadyRead() дописываю замкнутый цикл forever;

запускаю Winternals Process Explorer и смотрю какой из потоков нагружен. как ни странно загружен главный поток..

пока писал этот пост пришла на ум попробывать вот так:
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(onReadyRead()),Qt::DirectConnection);


с Qt::DirectConnection слот onReadyRead() отрабатывает в дочернем потоке =)
и главное, при подвисшем потоке сервер продолжает принимать соединения и отвечать другим пользователям =)

Цитата(ViGOur)
Тогда тебе нужно отказаться от схемы "1 соединени - 1 поток", а подумать о реализации схему "много соединений - 1 поток".

вот наоборот не вижу смысла отказываться. т.к. при ддос атаке большинство коннектов будут отвергаться потоком принимающим соединения, из за авторизации. а те пользователи которые уже авторизированы будут рабатать без проблемм. мало того, если авторизированый пользователь захочет навредить системе, он сможет подвесить только себя. отвалится по tcp-таймауту =)) т.к. его соединение обрабатывает тот же поток что и все остальные функции обработки действий пользователя =)

таким образом хочу максимально изолировать пользователей друг от друга. планируется около 1-2 тыс авторизированых пользователей. мм.. можно ли создать такое колличество потоков?

хранить указатель на соединение это конечно удобно.. а как лучше сделать если всётаки 1 поток = 1 соединение?)

сам себе отвечу =)
http://rsdn.ru/archive/vc/issues/pvc064.htm
Таким образом максимальное количество потоков, которые можно создать при всех параметрах заданных по умолчанию, равняется примерно 2035
Все сказанное ниже справедливо для линейки Windows NT/2000/XP

для линукса наверное будет другое колличество, но думаю мне хватит =)

Автор: ViGOur 11.11.2008, 13:23

Цитата(512es @ 11.11.2008, 11:53) *
а как лучше сделать если всётаки 1 поток = 1 соединение?)
Все также, только схема другая...

Автор: Litkevich Yuriy 11.11.2008, 15:05

Мысль в слух:
Нельзя ли сделать один поток для авторизации, и группу потоков для авторизованых пользователей, т.е. когда несколько соединений, например 10, крутятся в одном потоке, если авторизованый пользователь начнет гадить, то навредит только десятерым, а остальные нормально работают.

Вобще в юниксах обычно делают одно соединение - один процесс, админы говорят, что так удобнее следить, и когда явно надо кого-то сбросить, то вот его Id, вот его и будем Килл.

(в Unix-системах нет разделения понятий поток/процесс, там всё - потоки. Процессом обычно называют главный поток программы.)

Автор: 512es 12.11.2008, 0:43

Цитата(Litkevich Yuriy)
если авторизованый пользователь начнет гадить, то навредит только десятерым

потенциальная дыра.. таким способом можно по 9 пользователей убивать за заход =)

значит, вместо QVector теперь использую QHash (быстрее икать пользователя по иду)
дочерние потоки очень хорошо и удобно передают данные главному через customEvent. мало того, евенты складываются в очередь и это тоже меня устраивает. т.е. обрабатываются в порядке поступления.

а вот отправка данных потоку не получается у меня как надо..
если вызывать метод напрямую через указатель на поток, хранящийся в QHash они выполняются всё равно в главном потоке.
если эмитом вызывать слот результат такой же

через сигнал\слот что то не получилось, т.к. надо передать ещё несколько переменных..

пробовал в дочерние потоки вставлять обработчик customEvent, эвенты приходят но всё равно выполняются в главном потоке =(

в результате отсылка пакета 100 пользователям происходит очень долго. не смотря на то что они все подключались через локалхост.

было бы идеальным быстро распарралелить по всем потокам процедуру отправки пакета по tcp не ждать отправки каждого..

завтра попробую в класс дочерних потоков добавить некую очередь, защищённую мьютексом, в которую буду добавлять пакеты, ожидающие отправки..

ииии... как то всётаки надо сообщить потоку о том что пора отправлять пакет =(

не пойму, почему я не могу из родительского потока пробиться в дочерний??

Автор: ViGOur 12.11.2008, 8:29

Цитата(512es @ 12.11.2008, 0:43) *
через сигнал\слот что то не получилось, т.к. надо передать ещё несколько переменных..
Почему не получилось? Не вижу проблем в передаче каких бы то ни было переменных!

Автор: 512es 12.11.2008, 13:06

вообще, это мой первый проект на Qt, потому прошу сильно ногами не бить =)

вот как я пытаюсь делать:

class NetServer : public QTcpServer
{
    Q_OBJECT
public:
    NetServer(QObject *parent = 0);
    QHash<int, PThread*> Users;
private:
    int CurrentThreadNum;
protected:
    void incomingConnection(int socketDescriptor);
    void customEvent(QEvent *event);
};

void NetServer::incomingConnection(int socketDescriptor)
{
    Users.insert(++CurrentThreadNum,new PThread(socketDescriptor, this));
//    connect(this, SIGNAL(toSendMessage(QString msgString)), Users.value(CurrentThreadNum), SLOT(onSendMessage(QString msgString)));
    Users.value(CurrentThreadNum)->ThreadNumber = CurrentThreadNum;
    Users.value(CurrentThreadNum)->start();
}

void NetServer::customEvent(QEvent *event)
{
    QHashIterator<int, PThread*> i(Users);
    switch ((int)event->type()) {
    case 1234:
        MyNewMsgEvent *MyMsgEvent = (MyNewMsgEvent *)event;
        qDebug() << "Received custom Event text from user" << MyMsgEvent->SenderUser << MyMsgEvent->message;      
        while (i.hasNext()) {
            i.next();
//            i.value()->onSendMessage(QString::number(MyMsgEvent->SenderUser,10) + " " + MyMsgEvent->message);
//            qDebug() << i.value() << "ddd" << Users.value(1) << i.value()->currentThread();
            MyNewMsgEvent *ev = new MyNewMsgEvent();
            ev->message = MyMsgEvent->message;
            ev->SenderUser = MyMsgEvent->SenderUser;
            QCoreApplication::postEvent(i.value(), ev);
        }
        break;
    case 1109: // User disconnected
        EventDisconnected *EvDisconnected = (EventDisconnected *)event;
        qDebug() << "User" << EvDisconnected->SenderUser << "SignOff";
        Users.value(EvDisconnected->SenderUser)->deleteLater();
        Users.remove(EvDisconnected->SenderUser);
        qDebug() << "Stats" << Users.size();
        break;
    }
}

void PThread::onDisconnected()
{
    exit();
    EventDisconnected *ev = new EventDisconnected();
    ev->SenderUser = ThreadNumber;
    QCoreApplication::postEvent(this->parent(), ev);
}

void PThread::run()
{
    tcpSocket = new QTcpSocket;
    ...
    connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(onReadyRead()),Qt::DirectConnection);
    connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(onDisconnected()),Qt::DirectConnection);
    exec();
    ...
    delete tcpSocket;
}

void PThread::customEvent(QEvent *event)
{
    switch ((int)event->type()) {
    case 1234:
        forever;
        break;
    }
}


знаю что многое ещё выгледит слишком не красиво, но сейчас мне нравное разобраться с потоками..
в дальнейшем вынесу всё управление пользователями в отдельный класс..

так вот, проблема в том что PThread::customEvent(QEvent *event) выполняется почему то в главном потоке( хотя по идее должен в дочернем..
это можно наглядно увидеть через Process Explorer. когда доходит до цикла forever вешается главный поток, а не дочерний(

Автор: 512es 12.11.2008, 15:02

попробовал через сигнал\слот..

connect(this, SIGNAL(toSendMessage(QString)), Users.value(CurrentThreadNum), SLOT(onSendMessage(QString)),Qt::DirectConnection);

всё равно в том же потоке выполняется =(

Автор: 512es 13.11.2008, 12:27

вообщем, всю голову сломал, не знаю как сделать(

http://trolltech.com/developer/faqs/faq.2007-10-02.9810372345
написано что есть 3 способа:
1) передача эвентов
2) QCoreApplication::invokeLater()
3) и сигнал\слот с QueuedConnection

но к сожалению всё сказаное там для явы..

пробовал эвенты и через сигнал\слот с QueuedConnection.

пробовал даже вызывать метод в котором эмитится сигнал по которому вызывается нужный слот, как тут:
http://forum.sources.ru/index.php?showtopic=245838&hl=

результат один и тот же(


обгуглил весь инет, народ, не ужели никто не знает как вызывать метод из класса дочернего потока чтобы он выполнился в том самом дочернем а не в главном?


на ум приходит ещё один оч кривой способ..
отказаться от родителя в дочерних потоках и добавить в нужные методы moveToThread(this)
иии.. вот только что родилась идея ещё хуже.. пересылать данные по TCP на локалхост соединению между главным и дочерними потоками =)) бред..

Автор: ViGOur 13.11.2008, 13:17

Цитата(512es @ 13.11.2008, 12:27) *
не ужели никто не знает как вызывать метод из класса дочернего потока
Я знаю, но судя по обсуждению ты не понял, что и как делать, потому мне пример писать нужно, а времени пока увы нет. :(

Честно говоря там ничего сложного нет...

Автор: ViGOur 13.11.2008, 14:30

Вот я пример набросал, правда с рисованием, но думаю идея будет понятна... :)

Раскрывающийся текст
main.cpp
#include <QtCore/QCoreApplication>

#include "xThread.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    CxThread thread[2];
    for( int n=0; n < sizeof( thread)/sizeof( *thread); ++n)
    {
        thread[n].start();
    }

    while( true)
    {
        for( int n=0; n < sizeof( thread)/sizeof( *thread); ++n)
        {
            if( thread[n].isRunning())
                emit thread[n].drawText();
        }
        thread[0].sleep( 1);
        qDebug( "**********************");
    }

    return a.exec();
}

xThread.h
#ifndef XTHREAD_H
#define XTHREAD_H

#include <QThread>
#include <QMutex>

class CxPrint;

class CxThread : public QThread
{
    Q_OBJECT

private:
    static QMutex m_mutex;
    int        m_n;
    CxPrint *m_pPrint;

public:
    CxThread(QObject *parent=0);
    ~CxThread();

    void drawText();
    static void sleep ( unsigned long uls);
    int GetN()const { return m_n; }

protected:
    virtual void run();
    
};

class CxPrint: QObject
{
    Q_OBJECT

public:
    void drawText();

signals:
    void signalDrawText();

protected slots:
    void slotDraw();

public:
    CxPrint(void);
    virtual ~CxPrint(void);
};


#endif // XTHREAD_H

xThread.cpp
#include "xThread.h"
#include "xPrint.h"

QMutex CxThread::m_mutex;

CxThread::CxThread(QObject *parent)    : QThread(parent)
{
    m_n=0;
    m_pPrint = 0;
}

CxThread::~CxThread()
{
    delete m_pPrint;
    m_pPrint = 0;
}

void CxThread::drawText()
{
    if( m_pPrint)
        emit m_pPrint->drawText();
}

void CxThread::sleep ( unsigned long uls)
{
    QThread::sleep( uls);
}

void CxThread::run()
{
    {
        QMutexLocker locker(&m_mutex);
        static int n = 1;
        m_pPrint = new CxPrint;
        qDebug( "Runing thread with id: 0x%x, thread N: %d", QThread::currentThreadId(), n);
        m_n = n;
        n++;
    }

    CxThread::exec();
}

CxPrint::CxPrint(void)
{
    connect( this, SIGNAL( signalDrawText()), this, SLOT( slotDraw()));
}

CxPrint::~CxPrint(void)
{
}

void CxPrint::slotDraw()
{
    qDebug( "Draw in thread with id: 0x%x, thread N: %d", QThread::currentThreadId(), ((CxThread*)QThread::currentThread())->GetN());
}

void CxPrint::drawText()
{
    emit signalDrawText();
}

Автор: 512es 13.11.2008, 16:42

ViGOur, спасибо!) то что надо!))
у меня рождалась мысль что объект надо создавать в секции run() но уже не верилось что это заработает..
вообще жудко запутаная конструкция получилось, жаль что нельзя упростить.. хотяя...

Автор: ViGOur 13.11.2008, 18:01

Цитата(512es @ 13.11.2008, 16:42) *
ViGOur, спасибо!) то что надо!))
Не за что, обращайся еще... ;)

Цитата(512es @ 13.11.2008, 16:42) *
жаль что нельзя упростить..
Можно.

Автор: 512es 15.11.2008, 1:19

разве что от CxThread::drawText() удалось избавиться)

emit thread[n].drawText().m_pPrint.drawText()


вообще сервер работает быстро и надёжно) сколько бы не подключалось клиентов, пакеты им приходят моментально!)
отказался от эвентов, использую везде теперь только сигнал\слот =)

Автор: Litkevich Yuriy 15.11.2008, 1:29

Цитата(512es @ 15.11.2008, 4:19) *
вообще сервер работает быстро и надёжно) сколько бы не подключалось клиентов, пакеты им приходят моментально!)
Ну вот, а на пргорге тема о высоконагруженном сервере на Qt зашла в тупик (http://www.prog.org.ru/index.php?topic=7889)

Автор: ViGOur 15.11.2008, 10:28

Цитата(Litkevich Yuriy @ 15.11.2008, 1:29) *
Ну вот, а на пргорге тема о высоконагруженном сервере на Qt зашла в тупик
Просто они не в ту сторону смотрели... :)

512es, и все же я тебе рекомендую попробовать в одном потоке держать несколько подключений.
Попробуй сделать одно оооочень медленное (например передачу огромного файла), и после этого 5-10 быстрых соединений, посмотри как они обработаются... ;)

Автор: 512es 20.11.2008, 13:52

ViGOur, провёл эксперимент:
подключился двумя клиентами. один из них отправлял файл на 200 мб а второй быстро посылал маленькие пакеты серверу и получал ответ. пока файл отправлялся подключился ещё двумя клиентами и тоже быстро отсылал небольшие пакеты. задержек вообще никаких не наблюдалось!) всё работало так, какбудто 200 меговый файл и не принимался.
разве что, когда закончилась передача файла и он поступил в главный поток для обработки (в данном случае для записи на диск), отсылка ответов клиентам приостановилась ненадолго. но все пакеты скапливались в очередь и сразу обработались как только запись файла завершилась =)
а если такие большие пакеты посылать в отдельный специальный поток, с низким приоритетом то вообще хоть терабайты пересылать можно, никто и не заметит =)

Автор: ViGOur 20.11.2008, 15:21

Цитата(512es @ 20.11.2008, 13:52) *
а если такие большие пакеты посылать в отдельный специальный поток, с низким приоритетом то вообще хоть терабайты пересылать можно, никто и не заметит =)
Вот и я об этом же, потому и рекомендую экономить ресурсы, и повесить хотя бы по 32 соединения в поток, а не 1. :)

32 соединения в поток, 320 соединений, это не 320 потоков, а 10, разница есть?

Автор: 512es 20.11.2008, 16:59

тут просто разные нужды. мне важно чтобы все юзвери ансинхронно получали и отправляли маленькие пакеты. без задержек и очередей на приём\отправку. и оно отлично работает когда на каждого отдельный поток.

хотя я и планирую иногда передавать от сервера к юзверем файлы не больше 1 мб, это не файлообменник, где можно экономить на времени отклика, выстраивая отправку\приём в очередь.
вот наверное поэтому я всё никак с вами не соглашусь =))

32 соединения в поток, 320 соединений, это не 320 потоков, а 10, разница есть?

ХР может спокойно работать с 2032 потоками. 2000 пользователей меня вполне устраивают =) каких то особых тормозов от создания\удаления потоков или поедания памяти я не вижу. а ещё рассчёт на многопроцесорные машины на мноого лет вперёд))))

Цитата(ViGOur)
Цитата(512es)
а если такие большие пакеты посылать в отдельный специальный поток, с низким приоритетом то вообще хоть терабайты пересылать можно, никто и не заметит =)

Вот и я об этом же, потому и рекомендую экономить ресурсы, и повесить хотя бы по 32 соединения в поток, а не 1.

тут немного ни о том речь))
1) есть много много потоков, по 1 на юзверя (с низким приоритетом)
2) есть главный поток в котором очередь обработки (логика)

когда пакет неспеша пришёл от дочернего потока он поступает в главный, там очередь на обработку. всё это обычно делается очень быстро, путём рассылки сигналов дочерним потокам.
но вот в случае с файлом.. чтобы записать большой файл на диск потребуется время. потому я и сказал что можно для таких длительных операций сделать отдельную очередь обработки, чтобы не замедлять остальных

вот и всё =)

Автор: ViGOur 20.11.2008, 20:21

Цитата(512es @ 20.11.2008, 16:59) *
тут немного ни о том речь))
Понятно, вам выбирать принцип работы... :)

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)