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

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

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Запись сокета из разных потоков  (Прочитано 12888 раз)
dead_vip
Гость
« : Октябрь 26, 2011, 19:08 »

Делаю лабу - чат с использованием tcp-сокетов. Написал 2 программки - клиент и сервер (сервер работает в единственном потоке), все работает, но для лучшего понимания хочу переделать сервер следующим образом: онсновной поток должен только принимать данные (обрабатывать сигналы по-сути) и передавать в отдельный поток, который будет обрабатывать данные и писать в сокет. Проблема в том, что сокеты создаются в главном потоке и сигналы обрабатываются тоже в главном потоке, при создании потока для обработки туда передается указатель на сокет, в теле run() вызывается QTcpSocket.write и вываливается ошибка "QObject: Cannot create children for a parent that is in a different thread.". Не пойму что не так, ведь слоты сокета обрабатываются в том же потоке, откуда идет сигнал (в главном потоке), а в паралельных потоках я всего лишь записываю в сокет...

Я много искал в интернете по поводу того, как адекватно реализовать мою задумку, но ничего толкового так и не нашел. Везде пишут элементарные вещи типа: как ответить одному клиенту (там обычно сокет создается в паралельном потоке и висит при помощи exec(), обрабатывая сигналы), в примере threaded fortune server, который идет вместе с qt вообще тупо создается поток, в теле run создается сокет, отсылает 1 строчку и закрывает сокет, имхо это бред, а не пример.

Для тех кто так и не понял в чем вопрос, кратко его можно сформулировать: "как передать в поток список открытых сокетов (или же один сокет), чтобы он записал во все сокеты (или один) данные и уничтожился, не закрывая сокеты?"

Вот куски кода (во вложении все)
Код:
//myserver.cpp - тут отлавливаются сигналы incomingConnection и создается обьекты MyClient
//MyServer - производный от QTcpServer
void MyServer::incomingConnection(int handle)
{
    MyClient *client = new MyClient(this, &_clients);

    client->setSocket(handle);//назначить дескриптор (QTcpSocket.setSocketDescriptor())
    _clients.append(client);//добавить в QList
}

Код:
//myclient.cpp - тут (главный поток) обрабатываются сигналы
//MyClient - не производный от QTcpSocket, тут есть переменная-сокет QTcpSocket *_sok;
void MyClient::setSocket(int desc)
{
    _sok = new QTcpSocket(this);
    _sok->setSocketDescriptor(desc);
//тут были пляски с типами соединений
    connect(_sok, SIGNAL(connected()), this, SLOT(onConnect()), Qt::QueuedConnection);
    connect(_sok, SIGNAL(disconnected()), this, SLOT(onDisconnect()), Qt::QueuedConnection);
    connect(_sok, SIGNAL(readyRead()), this, SLOT(onReadyRead()), Qt::DirectConnection);
    connect(_sok, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)), Qt::QueuedConnection);
}

void MyClient::onReadyRead()
{
//считывается размер входящего блока и пока он не прийдет полностью обработка не начинается
    QDataStream in(_sok);
    if (_blockSize == 0) {
        if (_sok->bytesAvailable() < (int)sizeof(quint16))
            return;
        in >> _blockSize;
        qDebug() << "_blockSize now " << _blockSize;
    }
    if (_sok->bytesAvailable() < _blockSize)
        return;
    if (_sok->bytesAvailable() >= _blockSize)
        _blockSize = 0;
//теккущая команда серверу - второй байт в блоке
    quint8 command;
    in >> command;
    qDebug() << "received command " << command;
//все команды кроме запроса авторизации от неавторизованного пользователя игнорируются
    if (!_isAutched && command != comAutchReq)
        return;
//попытка сделать через QThread
    t *t1 = new t(NULL, this);
    t1->start();
//попытка сделать через QThreadPool
    //MyTask *task = new MyTask(this, command);
    //task->setAutoDelete(true);

    //QThreadPool::globalInstance()->start(task);
    /*
    switch(command)
    {
    //тут обработка данных идет в главном потоке, нужно ее перенести в паралельный поток (выше попытки сделать это)
    }*/

    //for (long long i = 0; i < 4000000000; ++i){}
}

Код:
//t.cpp - обработка в потоке
t::t(QObject *parent, MyClient *c) : QThread(parent)
{
//передан указатель на MyClient
    _c = c;
}

void t::run()
{
    _c->_sok->write("fgds");//QObject: Cannot create children...
    //вызов метода MyClient тот же результат
    //_c->doSendUsersOnline();//QObject: Cannot create children
    qDebug() << "gfdg";
    exec();
}

« Последнее редактирование: Октябрь 26, 2011, 19:25 от dead_vip » Записан
Странник
Гость
« Ответ #1 : Октябрь 26, 2011, 20:12 »

вызывать методы QTcpSocket можно только из того потока, в котором он живет. еще вопросы?
Записан
BRE
Гость
« Ответ #2 : Октябрь 26, 2011, 20:17 »

Подобные темы точно были. Поищи по форуму и найдешь решение.
Записан
dead_vip
Гость
« Ответ #3 : Октябрь 26, 2011, 22:09 »

вызывать методы QTcpSocket можно только из того потока, в котором он живет. еще вопросы?
Похоже так и есть... Получается палка о двух концах - я не могу делать moveToThread потому, что слоты disconnect(), readyRead() должны срабатывать в главном потоке, не могу создать еще один сокет с таким же дескриптором, и я не могу использовать уже открытый сокет в другом потоке, но почему? Именно это мне и нужно...
Записан
BRE
Гость
« Ответ #4 : Октябрь 26, 2011, 22:23 »

Лучше начать сначала... Что ты хочешь получить?
Основной поток принимает подключение, создает объект MyClient, дальше этот объект можно переместить в отдельную нить используя moveToThread, где он и будет работать.
Записан
dead_vip
Гость
« Ответ #5 : Октябрь 26, 2011, 22:41 »

Верно, но тогда сигнал readyRead будет обрабатываться в этой отдельной нити и она должна будет висеть пока не отключится клиент, это плохо и задумка не такая.
Основной поток принимает подключение, создает объект MyClient, когда срабатывает слот MyClient.onReadyRead (в основной нити), в его теле создается отделная нить, которая должна принять указатель на сокет, в который она должна записать данные и уничтожиться. При этом (после создания отдельной нити, передав туда указатель на сокет) основная нить дальше обрабатывает слоты onReadyRead, не задумываясь об ответе клиентам (это перекладывается на отдельные нити).
Записан
BRE
Гость
« Ответ #6 : Октябрь 26, 2011, 22:55 »

Верно, но тогда сигнал readyRead будет обрабатываться в этой отдельной нити и она должна будет висеть пока не отключится клиент, это плохо и задумка не такая.
Основной поток принимает подключение, создает объект MyClient, когда срабатывает слот MyClient.onReadyRead (в основной нити), в его теле создается отделная нить, которая должна принять указатель на сокет, в который она должна записать данные и уничтожиться. При этом (после создания отдельной нити, передав туда указатель на сокет) основная нить дальше обрабатывает слоты onReadyRead, не задумываясь об ответе клиентам (это перекладывается на отдельные нити).
Операция QTcpSocket::write на самом деле просто копирует данные во внутренний буфер. Сама отправка будет происходить асинхронно, во время работы цикла обработки событий. Дальше данные будут помещаться в буфер отправки tcp-стека и только потом резаться на пакеты и отправляться клиенту. Это я к тому, что нет смысла выполнять запись в сокет в отдельном потоке, эта операция, как правило, выполняется очень быстро.
Можно делать так: одна нить занимается подключением/чтением/записью для всех клиентов, а все долгие операции по подготовке данных для отправки - выполняются в рабочих нитках.
Записан
Странник
Гость
« Ответ #7 : Октябрь 26, 2011, 23:13 »

либо основной поток занимается подключением клиентов, а чтение, обработка и запись выполняются в отдельном потоке для каждого клиента. хотя в случае с чатом многопоточность не так актуальна - объемы данных как правило невелики, и особой обработки им не требуется.
Записан
dead_vip
Гость
« Ответ #8 : Октябрь 26, 2011, 23:43 »

Можно делать так: одна нить занимается подключением/чтением/записью для всех клиентов, а все долгие операции по подготовке данных для отправки - выполняются в рабочих нитках.
Видел туториал на ютубе, где такое реализовывали, сначала подумал зачем все так запутанно, но все-равно сделал (после обработки данных в отделной ните, она посылает сигнал основной и оттуда вызывается write(), а отдельная уничтожается), но потом рассказал эту схему преподaвателю, он сказал, что сделал бы так, как описано в первом посте, и я решил попробовать сделать именно так. Дело в том, что он ориентируется на Delphi, а там похоже там совсем другой принцип.

Спасибо за ответы.
« Последнее редактирование: Октябрь 27, 2011, 00:10 от dead_vip » Записан
andrew.k
Гость
« Ответ #9 : Октябрь 26, 2011, 23:44 »

2 BRE , ну это все-таки лаба) Поэтому почему бы не быть потокам?

В MyClient передавай не указатель на сокет, а socketDescriptor.
А сам сокет создавай в MyClient. И там же все и пиши и читай, зачем основному потоку заниматься записью данных других потоков? Что вообще тогда делают эти потоки?

Ниже примерный псевдокод.
Код
C++ (Qt)
class MyClient : public QThread
{
 MyClient(int socket )
 {
    _socket = socket;
 }
 void run()
 {
    QTcpSocket socket;
    socket.setSocketDescriptor( _socket );
    ...
    exec();
 }
}
...
MyServerSocket::incomingConnection( int socketId )
{
  MyClient * c = new MyClient( socketId );
  connect( c, SIGNAL( finished() ), c, SLOT( deleteLater() ) );
  c->start();
}
Записан
dead_vip
Гость
« Ответ #10 : Октябрь 27, 2011, 00:27 »

В MyClient передавай не указатель на сокет, а socketDescriptor.
А сам сокет создавай в MyClient. И там же все и пиши и читай, зачем основному потоку заниматься записью данных других потоков?
Это вариант, но тогда в памяти будет висеть столько потоков (даже не просто висеть, а тратить время процессора?), сколько подключенных клиентов, даже если никто никому ничего не пишет.
Что вообще тогда делают эти потоки?
В теле идет разбор данных от клиента - если это общее сообщение поток должен разослать его всем активным сокетам, если приватное - то только тем, кому оно послано, если запрос на авторизацию - считать и проверить имя и возможно отправить список имен "кто онлайн".
Фишка в том, что если есть активность - создаются потоки, отрабатывают и уничтожаются, а иначе висит лишь один поток, а не много.
« Последнее редактирование: Октябрь 27, 2011, 00:29 от dead_vip » Записан
andrew.k
Гость
« Ответ #11 : Октябрь 27, 2011, 01:06 »

тогда я не понимаю зачем этим потокам вообще указатель на сокет это раз, если они в них не пишут, только данные обрабатывают.
не понимаю зачем потоки для таких элементарных операций это два.
они создаваться и удаляться будут дольше чем реально работать.
Записан
dead_vip
Гость
« Ответ #12 : Октябрь 27, 2011, 01:34 »

Похоже сделать так, чтобы потоки еще и писали данные нельзя (но идея была именно, чтобы писали).

А нужны они просто для понимания (просто я подумал: "А если операции будут не элементарные", и решил переделать с использованием потоков).
Записан
andrew.k
Гость
« Ответ #13 : Октябрь 27, 2011, 02:57 »

почему нельзя то? смотри ответ #9.
я че зря писал? че ты ничего не понял?
в такой реализации пиши себе на здоровье в сокет.
Записан
Странник
Гость
« Ответ #14 : Октябрь 27, 2011, 08:24 »

не понимаю зачем потоки для таких элементарных операций это два.
ну это все-таки лаба) Поэтому почему бы не быть потокам? (C) <= )

баловство это все. даже чисто для понимания - не поймешь ничего толком, когда реализация на задаче смотрится примерно как на корове седло. хочешь разобраться - напиши в свободное время какой-нибудь более нагруженный сервер, например, файлы клиентам раздающий. тогда плюсы и минусы каждого решения прочувствуешь на себе.
Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


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