Russian Qt Forum

Qt => Работа с сетью => Тема начата: bvn13 от Январь 31, 2010, 23:01



Название: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: 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.. //почему второй раз???


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


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: niXman от Январь 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;
};
 


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: bvn13 от Январь 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() ?


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: BRE от Январь 31, 2010, 23:21
Желательно сначала разобраться с потоками по лучше.
Потоки запускаются методом start(), а не run().
В дочерних потока нельзя использовать GUI.
Передача объектов-наследников QObject от потока в поток выполняется с помощью void QObject::moveToThread ( QThread * targetThread ), причем у этих объектов не должно быть владельца (parent = 0).
Воспользуйся поиском, эту тему уже несколько раз обсуждали в мельчайших подробностях.  ;)


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: maxxant от Январь 31, 2010, 23:26

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

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

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

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

    if (blockSize == 0)
        ...
 {



Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: bvn13 от Январь 31, 2010, 23:26
Желательно сначала разобраться с потоками по лучше.
пытаюсь, но что-то нормальной литературы не могу раздобыть... везде эти FortuneServer|FortuneClient... они меня уже бесят своей минималистичностью..  >:(

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

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

Передача объектов-наследников QObject от потока в поток выполняется с помощью void QObject::moveToThread ( QThread * targetThread ), причем у этих объектов не должно быть владельца (parent = 0).
Воспользуйся поиском, эту тему уже несколько раз обсуждали в мельчайших подробностях.  ;)
да, это я тоже встретил на этом форуме, спс, буду глядеть. но насчет этого метода есть встречный вопрос. Если (взять тот же ThreatedFortuneServer) в типовых примерах есть реализация как у меня, то почему так нельзя? или это неправильно просто? но тогда опять же - почему так написано в примере от разработчиков?


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: bvn13 от Январь 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) // и тут же проверяем на ноль... зачем?
        ...
 {


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: BRE от Январь 31, 2010, 23:31
а я видел в доках, что run()... хм...
run - это тело потока.

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

да, это я тоже встретил на этом форуме, спс, буду глядеть. но насчет этого метода есть встречный вопрос. Если (взять тот же ThreatedFortuneServer) в типовых примерах есть реализация как у меня, то почему так нельзя? или это неправильно просто? но тогда опять же - почему так написано в примере от разработчиков?
В примерах по другому...
Между потоками передается хендл сокета, а не объект.


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: bvn13 от Январь 31, 2010, 23:35
а я видел в доках, что run()... хм...
run - это тело потока.

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

да, это я тоже встретил на этом форуме, спс, буду глядеть. но насчет этого метода есть встречный вопрос. Если (взять тот же ThreatedFortuneServer) в типовых примерах есть реализация как у меня, то почему так нельзя? или это неправильно просто? но тогда опять же - почему так написано в примере от разработчиков?
В примерах по другому...
Между потоками передается хендл сокета, а не объект.
что есть хендл сокета? его дескриптор? ну так я его и передаю в одном из вариантов... и судя по отсутствию ошибок - он успешно доходит...


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: BRE от Январь 31, 2010, 23:47
что есть хендл сокета? его дескриптор? ну так я его и передаю в одном из вариантов... и судя по отсутствию ошибок - он успешно доходит...
Значит в этом варианте не нужно делать moveToThread. А если захочешь передать объект, то понадобиться.  :)
У тебя я нигде в run не увидел запуска цикла обработки событий, а без него система межпоточных сообщений работать не будет.

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

Лучше сначала хорошо разобраться с потоками, потом хорошо разобраться с сокетами, а потом пытаться использовать эти механизмы вместе. Иначе ты будешь только путаться и не понимать из-за чего что не работает.  ;)


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: maxxant от Январь 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() может прийти как несколько пакетов, так и часть от одного, и в цикле приходиться проверять и считывать, исходя из того сколько байт доступно в буфере сокета и каков должен быть размер пакета.


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: bvn13 от Январь 31, 2010, 23:54
Новый поток создается не в конструкторе QThread, а после вызова start. Т.е. можно сказать, что контекст потока образуется при входе в тело потока (метод run) и разрушается он при выходе из нее. Все объекты, которые будут созданы в run, будут находиться в его контексте, любые другие объекты созданные до момента входа в run нужно переносить в него с помощью moveToThread. Конечно, если с этими объектами предполагается работать в этом потоке.

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


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: maxxant от Февраль 01, 2010, 00:14
эти объекты я смогу нормально использовать в теле run() или все-таки moveToThread?  ;)

всё что не создаётся в теле run() нужно делать moveToThread() и только после этого коннектить к ним сигналы/слоты (или вручную указывать при коннекте Qt::QueuedConnection). При этом помнить, что если в треде (в run() ) нет фунции exec() или QCoreApplication::processEvents() то и слоты которые QueuedConnection в этом треде обрабатываться не будут. Вообщем, курить доки. Я по началу три раза переписывал Qt-шный сетевой код, пока просветление не наступило.


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: BRE от Февраль 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;
};
 


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: hackoff от Февраль 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() - это я глупость написал :(, но сути вопроса не меняет..


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: maxxant от Февраль 03, 2010, 10:58
Почему byteArray не копируется в QString, а при этом вот такая конструкция for (int k=0;k<8;k++) send.append((char)byteArray[k]); прекрасно собирает слово. Миллион и один способ пробовал, ничего не получается :(.

лучше миллион и один раз посмотреть доки с примерами. Вы сериализуете один тип, а десериализуется другой. Десериализация должна быть строго симметричной. Иначе результат не предсказуем. Можете для примера сравнить разницу операторов сериализации для QByteArray и QString:

Код
C++ (Qt)
QDataStream &operator<<(QDataStream &out, const QByteArray &ba)
{
   if (ba.isNull() && out.version() >= 6) {
       out << (quint32)0xffffffff;
       return out;
   }
   return out.writeBytes(ba, ba.size());
}
 
QDataStream &operator<<(QDataStream &out, const QString &str)
{
   if (out.version() == 1) {
       out << str.toLatin1();
   } else {
       if (!str.isNull() || out.version() < 3) {
           int byteOrder = out.byteOrder();
           const QChar* ub = str.unicode();
           static const uint auto_size = 1024;
           char t[auto_size];
           char *b;
           if (str.length()*sizeof(QChar) > auto_size) {
               b = new char[str.length()*sizeof(QChar)];
           } else {
               b = t;
           }
           int l = str.length();
           char *c=b;
           while (l--) {
               if (byteOrder == QDataStream::BigEndian) {
                   *c++ = (char)ub->row();
                   *c++ = (char)ub->cell();
               } else {
                   *c++ = (char)ub->cell();
                   *c++ = (char)ub->row();
               }
               ub++;
           }
           out.writeBytes(b, sizeof(QChar)*str.length());
           if (str.length()*sizeof(QChar) > auto_size)
               delete [] b;
       } else {
           // write null marker
           out << (quint32)0xffffffff;
       }
   }
   return out;
}


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: hackoff от Февраль 03, 2010, 11:34
Идею понял! Спасибо!
Но появилась другая проблема.
В клиенте пишу
Код:
    QPixmap pix=screenShot();
    QIODevice * socket = (QIODevice*)this;
    QDataStream data(socket);
    data.setVersion(QDataStream::Qt_4_6);
    QString send("send-screen");
    data<<send<<pix<<QString("end");

На сервере читаю
Код:
void connectionTCP::ready_read(){
    QIODevice * socket = (QIODevice*)this;
    QDataStream data(socket);
    data.setVersion(QDataStream::Qt_4_6);
    QByteArray byteArray;
    QString str, str2;
    QPixmap pix;
    data>>str;
    data>>pix;
    data>>str2;
    qDebug()<<str<<pix.size()<<str2;
}

Дебаг выводит
Цитировать

"send-screen" QSize(-1, -1) ""
"" QSize(-1, -1) ""
"" QSize(-1, -1) ""
..................................
"" QSize(-1, -1) ""
"" QSize(-1, -1) ""

Почему изображение и последняя строка не читаются?


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: maxxant от Февраль 03, 2010, 12:44
Дебаг выводит
Цитировать

"send-screen" QSize(-1, -1) ""
"" QSize(-1, -1) ""
"" QSize(-1, -1) ""
..................................
"" QSize(-1, -1) ""
"" QSize(-1, -1) ""

Почему изображение и последняя строка не читаются?

Сигнал readyRead() присылается при получении пакета данных. Вероятно, когда вы его получаете, данные пришли ещё не все, сокет просто не может логически разделить пакеты так как вам необходимо . Возможно проблема сейчас и не в этом, но столкнуться с этим придётся, поэтому сразу реализуйте проверку на кол-во данных. Здесь на форуме уже писали (вроде и я писал) как поступить в этом случае. Есть метод bytesAvailable() у QIODevice и есть ваш пакет данных, размер которого необходимо знать до отправки, соответственно при приёме можно вычислить все ли необходимые данные уже находиться в буфере сокета, после чего считывать.
Если на вскидку, то всё просто, писать сначала в QByteArray а потом его и отправлять:

отправка:
Код
C++ (Qt)
   ...
   QByteArray msg;
   QDataStream stream(&msg, QIODevice::WriteOnly);
   stream.setVersion(QDataStream::Qt_4_6);
   stream << some_data;
   stream << some_data;
   stream << some_data;
 
   sendPacket(msg);
}
 
void sendPacket(QByteArray data)
{
   qint32 _size = data.size();
   if(data.size())
   {
       p_ioDevice->write((char*)&_size, sizeof(_size));
       p_ioDevice->write(data);
       qDebug() << "[net] send packet with size:" << data.size();
   }
}
 
quint32 чтобы работало везде на 32-х и на 64-х битах одинаково
p_ioDevice - это сокет.

получение:
Код
C++ (Qt)
 
void net::ready_read() // from signal readyRead()
{
   while(checkMessage())
   {
       qint32 _size=0;
       QByteArray data;
       // read packet size
       qint64 _readed = p_ioDevice->read((char*) &_size, sizeof(_size));
       if((sizeof(_size)==_readed) && (_size>0))
       {
          // read data
          data = p_ioDevice->read(_size);
          if(data.size() != _size)
          {
              // error!!!
              // disconnect,  ...
              return;
          }
 
          packet_ready(data);
       }
   }
}
 
bool net::checkMessage()
{
   // check packet size
   qint32 _size=0;
   qint64 _bytes_available = p_ioDevice->bytesAvailable();
   if(_bytes_available < sizeof(_size)) return false;
 
   qint64 _peek_size = p_ioDevice->peek((char*)&_size, sizeof(_size));
   if(_peek_size != sizeof(_size)) return false;
   if(0 == _size)
   {
       p_ioDevice->read(4); // empty packet
       return false;
   }
   _bytes_available = p_ioDevice->bytesAvailable();
   if(_bytes_available < sizeof(_size) + _size) return false;
   return true;
}
 
void net::packet_ready(QByteArray msg)
{
   QDataStream stream(&msg, QIODevice::ReadOnly);
   stream.setVersion(QDataStream::Qt_4_6);
   stream >> some_data
   stream >> some_data
   ...
}
 


Название: Re: Клиент-сервер(мультитрит): передача открытого сокета из одного Thread в другой
Отправлено: hackoff от Февраль 03, 2010, 12:48
спасибо большое. Попытаюсь применить Ваш рецепт.