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

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

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой  (Прочитано 12010 раз)
bvn13
Гость
« : Январь 31, 2010, 23:01 »

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

Сервер:

Код:
void MYthread::run()
{

        tcpServer->listen(QHostAddress::Any,DEFAULT_PORT);//)
    connect(tcpServer,SIGNAL(newConnection()),this,SLOT(slot_onNewConnection()));
}


void MYthread::slot_onNewConnection()
{

    //[1]+
    //QTcpSocket *newClientSocket = tcpServer->nextPendingConnection();
    //MYclientThread *newClientThread = new MYclientThread(newClientSocket->socketDescriptor(),this);

    //[1]-

    //[2]+
    MYclientThread *newClientThread = new MYclientThread(this);
    newClientThread->setClientSocket(tcpServer->nextPendingConnection());
    //[2]-

    newClientThread->run();
}


идея: при новом коннекте (slot_onNewConnection()) должен создаваться MYclientThread(public QThread), ему передаваться сокет, а он уже с этим сокетом обмениваться данными... но ни вариант 1, ни вариант 2 не работает. вернее...

вот прием сокета в классе MYclientThread:

Код:
MYclientThread::MYclientThread(int desc, QObject *parent) //конструктор
        : QThread(parent), socketDescription(desc)
{
    clientSocket = new QTcpSocket(this);
    connect(clientSocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(slot_onSocketError(QAbstractSocket::SocketError)));
    if (!clientSocket->setSocketDescriptor(socketDescription)) {
        //сюда вообще не заходит, т.е. дескриптор присваевается правильно...
        QMessageBox msgBox;
        msgBox.setText("MYclientThread Error setting descriptor");
        msgBox.exec();
        return;
    }
    clientInfo = 0;

}

void MYclientThread::slot_onDisconnecting()
{
    //А вот это событие обваливает сокет!!!

    QMessageBox msgBox;
    msgBox.setText("MYclientThread Something is wrong! - Disconnecting..");
    msgBox.exec();

    timer.singleShot(0, clientSocket, SLOT(deleteLater()));
    //clientSocket->deleteLater();
    this->exit(0);
}


void MYclientThread::run(void)
{
    QMessageBox msgBox;
    msgBox.setText("MYclientThread running");
    msgBox.exec();

    // бла-бла... прием, передача, прием...
}


сообщения появляются в следующей очереди:
1) MYclientThread running
2) MYclientThread Something is wrong! - Disconnecting..
3) MYclientThread Something is wrong! - Disconnecting.. //почему второй раз???


подскажите мне, пожалуйста, что я делаю не так? почему обваливается сокет? и как правильнее передать сокет от одного потока другому?
Записан
niXman
Гость
« Ответ #1 : Январь 31, 2010, 23:13 »

все не так. пиши с нуля.
псевдокод:
Код
C++ (Qt)
slot wait_incoming_connection(shared_ptr_to_socket) {
  new thread(shared_ptr_to_socket)
}
 
struct thread {
  thread(shared_ptr_to_socket s):socket(s) {
  }
protected:
  void run() {
     bla bla bla with socket
  }
private:
  shared_ptr_to_socket socket;
};
 
Записан
bvn13
Гость
« Ответ #2 : Январь 31, 2010, 23:19 »

ок, спасибо. перепишу.

ЗЫ! начал сейчас ставить мессаги по ходу приема-передачи данных на сервере и обнаружил, что валит прием данных!

вот код, который я стягивал с какого-то примера (то ли это threadedFortuneServer, то ли еще какой - не помню):

Код:
int MYclientThread::recieveFromClient_string(QString *result)
{
    QDataStream in(clientSocket);
    in.setVersion(QDataStream::Qt_4_0);
    quint16 blockSize;

    if (blockSize == 0) { //вот зачем тут эта проверка???
        if (clientSocket->bytesAvailable() < (int)sizeof(quint16))
            return 0;

        in >> blockSize;
    }

    if (clientSocket->bytesAvailable() < blockSize)
        return 0;

    in >> (*result);

    return blockSize;

}

от безысходности в понимании сего примера хочу спросить: как правильно считывать/записасывать данные из/в сокет(а)?

да, перед вызовом этого кода надо ставить waitForReadyRead() ?
« Последнее редактирование: Январь 31, 2010, 23:21 от bvn13 » Записан
BRE
Гость
« Ответ #3 : Январь 31, 2010, 23:21 »

Желательно сначала разобраться с потоками по лучше.
Потоки запускаются методом start(), а не run().
В дочерних потока нельзя использовать GUI.
Передача объектов-наследников QObject от потока в поток выполняется с помощью void QObject::moveToThread ( QThread * targetThread ), причем у этих объектов не должно быть владельца (parent = 0).
Воспользуйся поиском, эту тему уже несколько раз обсуждали в мельчайших подробностях.  Подмигивающий
Записан
maxxant
Гость
« Ответ #4 : Январь 31, 2010, 23:26 »


вот это порадовало:
Код:
newClientThread->run();

поток должен стартовать через start(), иначе это просто вызов функции.

и вот такого в примерах быть не могло, - сразу идет проверка не инициализированной переменной blockSize:

Код:
    QDataStream in(clientSocket);
    in.setVersion(QDataStream::Qt_4_0);
    quint16 blockSize;

    if (blockSize == 0)
        ...
 {

Записан
bvn13
Гость
« Ответ #5 : Январь 31, 2010, 23:26 »

Желательно сначала разобраться с потоками по лучше.
пытаюсь, но что-то нормальной литературы не могу раздобыть... везде эти FortuneServer|FortuneClient... они меня уже бесят своей минималистичностью..  Злой

Потоки запускаются методом start(), а не run().
а я видел в доках, что run()... хм...

В дочерних потока нельзя использовать GUI.
это я для отладки... потом уберу... кстати, а как логирование вести? в файл лить-то можно?

Передача объектов-наследников QObject от потока в поток выполняется с помощью void QObject::moveToThread ( QThread * targetThread ), причем у этих объектов не должно быть владельца (parent = 0).
Воспользуйся поиском, эту тему уже несколько раз обсуждали в мельчайших подробностях.  Подмигивающий
да, это я тоже встретил на этом форуме, спс, буду глядеть. но насчет этого метода есть встречный вопрос. Если (взять тот же ThreatedFortuneServer) в типовых примерах есть реализация как у меня, то почему так нельзя? или это неправильно просто? но тогда опять же - почему так написано в примере от разработчиков?
Записан
bvn13
Гость
« Ответ #6 : Январь 31, 2010, 23:30 »


вот это порадовало:
Код:
newClientThread->run();

поток должен стартовать через start(), иначе это просто вызов функции.


вот блин.... где же я это вычитал??? хм... спасибо Улыбающийся

и вот такого в примерах быть не могло, - сразу идет проверка не инициализированной переменной blockSize:

Код:
    QDataStream in(clientSocket);
    in.setVersion(QDataStream::Qt_4_0);
    quint16 blockSize;

    if (blockSize == 0)
        ...
 {

да, тут я апшибся... в fortuneClient (судя по логике вызовов методов) идет вот так:

Код:
    QDataStream in(clientSocket);
    in.setVersion(QDataStream::Qt_4_0);
    quint16 blockSize = 0; //присваиваем ноль

    if (blockSize == 0) // и тут же проверяем на ноль... зачем?
        ...
 {
Записан
BRE
Гость
« Ответ #7 : Январь 31, 2010, 23:31 »

а я видел в доках, что run()... хм...
run - это тело потока.

это я для отладки... потом уберу... кстати, а как логирование вести? в файл лить-то можно?
Для отладки нужно использовать qDebug()

да, это я тоже встретил на этом форуме, спс, буду глядеть. но насчет этого метода есть встречный вопрос. Если (взять тот же ThreatedFortuneServer) в типовых примерах есть реализация как у меня, то почему так нельзя? или это неправильно просто? но тогда опять же - почему так написано в примере от разработчиков?
В примерах по другому...
Между потоками передается хендл сокета, а не объект.
Записан
bvn13
Гость
« Ответ #8 : Январь 31, 2010, 23:35 »

а я видел в доках, что run()... хм...
run - это тело потока.

т.е. не надо реализовывать метод start()? оно само вызовет run() ? хм... читаю дальше маны...

да, это я тоже встретил на этом форуме, спс, буду глядеть. но насчет этого метода есть встречный вопрос. Если (взять тот же ThreatedFortuneServer) в типовых примерах есть реализация как у меня, то почему так нельзя? или это неправильно просто? но тогда опять же - почему так написано в примере от разработчиков?
В примерах по другому...
Между потоками передается хендл сокета, а не объект.
что есть хендл сокета? его дескриптор? ну так я его и передаю в одном из вариантов... и судя по отсутствию ошибок - он успешно доходит...
Записан
BRE
Гость
« Ответ #9 : Январь 31, 2010, 23:47 »

что есть хендл сокета? его дескриптор? ну так я его и передаю в одном из вариантов... и судя по отсутствию ошибок - он успешно доходит...
Значит в этом варианте не нужно делать moveToThread. А если захочешь передать объект, то понадобиться.  Улыбающийся
У тебя я нигде в run не увидел запуска цикла обработки событий, а без него система межпоточных сообщений работать не будет.

Новый поток создается не в конструкторе QThread, а после вызова start. Т.е. можно сказать, что контекст потока образуется при входе в тело потока (метод run) и разрушается он при выходе из нее. Все объекты, которые будут созданы в run, будут находиться в его контексте, любые другие объекты созданные до момента входа в run нужно переносить в него с помощью moveToThread. Конечно, если с этими объектами предполагается работать в этом потоке.

Лучше сначала хорошо разобраться с потоками, потом хорошо разобраться с сокетами, а потом пытаться использовать эти механизмы вместе. Иначе ты будешь только путаться и не понимать из-за чего что не работает.  Подмигивающий
Записан
maxxant
Гость
« Ответ #10 : Январь 31, 2010, 23:51 »

идея: при новом коннекте (slot_onNewConnection()) должен создаваться MYclientThread(public QThread), ему передаваться сокет

ещё вариант реализации наследоваться от QTcpServer и перегрузить
virtual void incomingConnection ( int socketDescriptor )
и из неё кидать дескриптор через сигнал в нужный поток, где и создавать сокет. Я так делал тоже.

по fortune client:

Код:
 void Client::requestNewFortune()
 {
     getFortuneButton->setEnabled(false);
     blockSize = 0;
     socket->abort();
     socket->connectToServer(hostLineEdit->text());
 }

 void Client::readFortune()
 {
     QDataStream in(socket);
     in.setVersion(QDataStream::Qt_4_0);

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

по сути здесь blockSize особого смысла не несёт, но вот в реальных приложениях, обычно передают размер блока в начале сообщения, поскольку по сигналу readyRead() может прийти как несколько пакетов, так и часть от одного, и в цикле приходиться проверять и считывать, исходя из того сколько байт доступно в буфере сокета и каков должен быть размер пакета.
Записан
bvn13
Гость
« Ответ #11 : Январь 31, 2010, 23:54 »

Новый поток создается не в конструкторе QThread, а после вызова start. Т.е. можно сказать, что контекст потока образуется при входе в тело потока (метод run) и разрушается он при выходе из нее. Все объекты, которые будут созданы в run, будут находиться в его контексте, любые другие объекты созданные до момента входа в run нужно переносить в него с помощью moveToThread. Конечно, если с этими объектами предполагается работать в этом потоке.

а, скажем, если я в своем thread-наследованном классе создам ссылочные свойства (объекты, которые мне понадобятся, объявлю ссылками), а перед запуском потока из метода, его запускающего, проинициализирую эти объекты, передав в них ссылки, на нужные мне объекты, - эти объекты я смогу нормально использовать в теле run() или все-таки moveToThread?  Подмигивающий
Записан
maxxant
Гость
« Ответ #12 : Февраль 01, 2010, 00:14 »

эти объекты я смогу нормально использовать в теле run() или все-таки moveToThread?  Подмигивающий

всё что не создаётся в теле run() нужно делать moveToThread() и только после этого коннектить к ним сигналы/слоты (или вручную указывать при коннекте Qt::QueuedConnection). При этом помнить, что если в треде (в run() ) нет фунции exec() или QCoreApplication::processEvents() то и слоты которые QueuedConnection в этом треде обрабатываться не будут. Вообщем, курить доки. Я по началу три раза переписывал Qt-шный сетевой код, пока просветление не наступило.
Записан
BRE
Гость
« Ответ #13 : Февраль 01, 2010, 00:16 »

а, скажем, если я в своем thread-наследованном классе создам ссылочные свойства (объекты, которые мне понадобятся, объявлю ссылками), а перед запуском потока из метода, его запускающего, проинициализирую эти объекты, передав в них ссылки, на нужные мне объекты, - эти объекты я смогу нормально использовать в теле run() или все-таки moveToThread?  Подмигивающий
Контекст, которому принадлежат объекты имеет значение только для объектов классов-наследников QObject, для того чтобы сигнальная система нормально работала между потоками.
Если такой объект создается не в методе run и с ним планируется настраивать сигнально-слотовые отношения,  то необходимо перемещение объекта в контекст потока.
Код
C++ (Qt)
class Thread : public QThread
{
public:
// Конструктор отработает в родительском потоке
Thread( QTcpSocket *client ) : m_socket( client )
{
Q_ASSERT( m_socket );
m_socket->moveToThread( this );
}
 
protected:
void run()
{
// Вот здесь поток создан и мы находимся в нем
// Все объекты созданные здесь будут относиться к контектсту этого потока.
QTimer timer; // Уже в контексте потока
connect( &timer, ... );
 
connect( m_socket, .... );
 
exec(); // Запускаем очередь обработки событий
}
 
private:
QTcpSocket *m_socket;
};
 
Записан
hackoff
Гость
« Ответ #14 : Февраль 02, 2010, 18:32 »

Тоже пишу аналогичное приложение. Проблема с чтением из сокета.

В потоке создаю класс, являющийся наследником QtcpSocket, создаю там слот ready_read(). Далее в конструкторе делаю так
Код:
    connect(this,SIGNAL(readyRead()),
            this,SLOT(ready_read()));

Так вот, когда я из КЛИЕНТА отправляю данный
Код:
QPixmap pix=screenShot();
    QIODevice * socket = (QIODevice*)this;
    QDataStream data(socket);
    data<<QString("send")<<QString::number(6)<<QString("screen")<<pix.size()<<screenShot()<<QString("end");

И пытаюсь получить на сервере в слоте ready_read
Код:
void connectionTCP::ready_read(){

    QByteArray byteArray;
    QIODevice * socket = (QIODevice*)this;
    QDataStream data(socket);
    QString send;
    int LengthCmd=0;
    QString cmd;
    QByteArray dataRaw;
    QString end("end");
    int i=0;
    while (!data.atEnd()){
        data>>byteArray;
        qDebug()<<"№"<<i++<<"byteArray size"<<byteArray.size()<<"QString"<<QString(byteArray)<<"QString size"<<QString(byteArray).size();
    }
}

qDebug() выводит
Цитировать
a?? 0 byteArray size 8 QString "" QString size 0
a?? 1 byteArray size 2 QString "" QString size 0
a?? 2 byteArray size 12 QString "" QString size 0
a?? 3 byteArray size 1680 QString "" QString size 0
a?? 4 byteArray size 0 QString "" QString size 0
...............................................................
a?? 0 byteArray size 0 QString "" QString size 0
a?? 0 byteArray size 0 QString "" QString size 0

Почему byteArray не копируется в QString, а при этом вот такая конструкция for (int k=0;k<8;k++) send.append((char)byteArray[k]); прекрасно собирает слово. Миллион и один способ пробовал, ничего не получается Грустный.

pix.size() - это я глупость написал Грустный, но сути вопроса не меняет..
« Последнее редактирование: Февраль 03, 2010, 08:26 от hackoff » Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


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