2016-02-11 1 views
0

Я довольно новичок в QThread, и я хочу сделать многопоточное приложение с Qt. Есть много онлайн-источников, некоторые рекомендуют подклассифицировать QThread, некоторые говорят, что это неверно, и они используют функцию moveToThread. Я действительно смущен. Возможно, это зависит от требований, поэтому я пишу свои требования.Нужна помощь для многопоточного приложения с использованием QThread

Программное обеспечение будет связываться с устройством по протоколу UDP. Устройство отправляет полученный пакет обратно отправителю в качестве ACK. Поскольку UDP не является надежным, я хочу добавить некоторую надежность в связь, проверив ответ с устройства. Я хочу дать немного времени устройству для отправки ответа. Если ответ (пакет ACK) не приходит, я хочу отправить тот же пакет снова. Если приходит неправильный пакет ответов (из пакетов с последовательностью), я могу игнорировать его, не нужно повторно их повторять. Будут некоторые пакеты (команды для устройства), которые будут периодически отправляться, например, каждую секунду.

Я хочу сделать все общение в потоке с помощью QThread. Поток запускается нажатием кнопки, и связь начнется (некоторые пакеты будут отправляться периодически). Связь остановится, нажав кнопку.

Так как я могу это сделать? Мне нужны только шаги.

EDIT:

Я уже impelemented блокирование чтения и записи. Я перемещаю его в Thread с помощью функции moveToThread и запускает поток. Но я не знаю, лучший ли это для моего случая использования.

я отправить несколько дейтаграмм, один за другим, как следующее, например:

int UDP::readCalibration(..) 
{ 
    while(readStatus() != 0); 

    // Send addr, read page command and read buffer command 
    // one after each 
    writeRegisterBlock(LOW_ADDR, CalibAddr, 2); // Blocking 
    writeRegister(CMD_Reg, READ_PAGE);   // Blocking 
    readRegisterBlock(READ_BUF, data, 128);  // Blocking 
} 
.... 
int UDP::writeRegisterBlock(...) 
{ 
    ....// Build UdpPacket 
    return UDP_Send(UdpPacket, &ReceivePacket); 
} 
int UDP::writeRegister(...) 
{ 
    ....// Build UdpPacket 
    return UDP_Send(UdpPacket, &ReceivePacket); 
} 
int UDP::readRegisterBlock(...) 
{ 
    ....// Build UdpPacket 
    return UDP_Send(UdpPacket, &ReceivePacket); 
} 
// and UDP_Send function, this is the important part 
int UDP::UDP_Send(QByteArray UdpPacket, QByteArray *ReceivePacket) 
{ 
    QByteArray Datagram; 
    QHostAddress SenderAddress; 
    quint16 SenderPort; 
    int UDP_RetVal = -1, timeOutCounter = -1, NrOfSend = -1, NrOfRecv = -1, retVal = -1; 
    bool pendingDatagram = false; 

    SetContinueToRecv(true); // sets a boolean variable with mutex 
    SetContinueToSend(true); // sets a boolean variable with mutex 

    while ((NrOfSend < MAX_RETRY) && GetContinueToSend()) 
    { 
     NrOfSend++; 
     SetContinueToRecv(true); // start receiver loop 
     UDP_RetVal = udpSocket->writeDatagram(UdpPacket, DestinationIP, port); 

     if(UDP_RetVal < 0) 
     { 
      qDebug() << "udpSocket error"; 
     } 
     else 
     { 
      while((NrOfRecv < MAX_RETRY) && GetContinueToRecv()) 
      { 
       pendingDatagram = false; 

       while (!pendingDatagram && (timeOutCounter < TIMEOUT_MS)) 
       { 
        msleep(SLEEP_MS); 
        timeOutCounter++; 
        pendingDatagram = udpSocket->hasPendingDatagrams(); 
       } 

       if (timeOutCounter < TIMEOUT_MS) 
       { 
        Datagram.resize(udpSocket->pendingDatagramSize()); 
        udpSocket->readDatagram(Datagram.data(), Datagram.size(), &SenderAddress, &SenderPort); 

        //Compare only first 4 bytes, cmd, nr, addr 
        if((UdpPacket.mid(0, 4) == Datagram.mid(0, 4)) && (SenderAddress == DestinationIP)) 
        { 
         retVal = 0; 
         //Take only data , not commands 
         ReceivePacket->resize(Datagram.size()-4); 
         ReceivePacket->replace(0, Datagram.size()-4, Datagram.right(Datagram.size()-4)); 
         SetContinueToRecv(false); // Break receiver while loop 
         SetContinueToSend(false); // Break sender while loop 
        } 
        else // Wrong packet 
        { 
         SetContinueToRecv(true); // try again to receive 
         timeOutCounter = -1; 
         qDebug() << "Wrong Packet."; 
        } // Wrong packet 
       } 
       else // Timeout 
       { 
        qDebug() << "Timeout maximum. NrOfSend: " << NrOfSend << " NrOfRecv: " << NrOfRecv; 
        timeOutCounter = -1; 
        SetContinueToSend(true); // try again to send 
        SetContinueToRecv(false); 
       } // Timeout 
      } // Receive loop 
      NrOfRecv = -1; 
     } // Succesfully sent 
    } // Send loop 
    if(NrOfSend == MAX_RETRY) 
    { 
     retVal = -1; // Could not send. 
     qDebug() << "Could not send"; 
    } 
    return retVal; 
} 

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

+0

Подклассификация 'QObject' и использование' moveToThread' - это, как правило, путь. Я думаю, что это хорошо объяснено в [документации] (http://doc.qt.io/qt-5/qthread.html#details). Вы еще это выяснили? – thuga

+0

Я кратко прочитал аргументы о том, как использовать QThread, и, как и вы, я пришел к выводу, что все это сбивает с толку, и я перестала читать. Суб-класс QThread и повторная реализация функции run() имеют для меня наибольший смысл. Относитесь к реализации run() как main() для нового потока. Создайте в нем объекты (в стеке), как и в main(). Следует помнить, что объекты, созданные в main(), относятся к основному потоку, а объекты, созданные в run(), относятся к этому потоку. Мне никогда не приходилось использовать функцию moveToThread(). thuga, и я вижу, что это по-другому выглядит :-( –

+0

Если вы хотите иметь класс реентера, который работает в другом потоке, то использование 'moveToThread' намного проще, чем подклассификация' QThread' для меня. Это позволяет использовать слоты вам не придется беспокоиться о блокировке мьютекса, а что нет при отправке сообщений по потокам. – thuga

ответ

3

Настоятельно рекомендую использовать асинхронный API, который класс QUdpSocket предоставляет для вашего использования.

Используйте слот, подключенный к сигналу readyRead(), и используйте readDatagram() в этом слоте для фактического считывания полученных данных из сокета. Чтобы отправить данные, используйте writeDatagram() везде, где захотите, он не блокирует вызывающий поток.

Вы можете прочитать Broadcast Receiver Example и Broadcast Sender Example и написать аналогичный код.

Что касается тайма-аута вещи, вы можете иметь QTimer с его timeout() сигналом, подключенного к слоту, который посылает дейтаграмму, так что он посылает его продолжает посылать пакет, когда ответ не приходит в определенное время, и у таймера start(), вызванный в слот, подключенный к сигналу readyRead() (так что таймер перезапускается каждый раз, когда принимается дейтаграмма).

Чтобы ответить на вопрос о многопоточности, обычный способ использования его вкратце состоит в том, чтобы иметь класс, который происходит от QObject, и реализовать слоты, которые должны быть выполнены в этом классе, а затем создать экземпляр нового QThread и позвонить moveToThread() на этом объекте к новому QThread. Впоследствии, когда какой-либо из сигналов, подключенных к слотам этого объекта, испускается, код слотов запускается в новом потоке.

Однако стоит отметить, что существует много способов использования многопоточности в Qt, и каждый прецедент имеет свой способ его реализовать, посмотрите здесь Multithreading Technologies in Qt. И в вашем случае использования НЕ используйте многопоточность.

EDIT: , чтобы вы не были в состоянии пойти в Интернете, и вы реализовали свой блокировки чтения и записи. , ,

Я двигаю его на тему, используя функцию moveToThread и запуска нити

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

Я вижу, что вы звоните msleep в свой UDP::UDP_Send. Пожалуйста, не говорите мне, что ваш класс UDP наследуется от QThread, снова прочитайте мой ответ, чтобы узнать, из чего вы должны получить.

кажется, что вы опросили QUdpSocket, позвонив по телефону hasPendingDatagrams каждые SLEEP_MS. если вы продолжите режим блокировки, используйте вместо этого waitForReadyRead таким образом, чтобы поток продолжал блокировать до момента, когда QUdpSocket получает дейтаграмму. , ,

Это признаки того, что вы не получаете свой код для выполнения в новой теме. , ,

P.S. : переписывание кода с использованием асинхронного способа, о котором я вам впервые рассказывал, намного проще, проще и чище, чем исправление вашего текущего кода. Просто прочитайте приведенные мной примеры и не стесняйтесь спрашивать о них, если вы что-то не понимаете. , ,

+0

Я не могу выходить в интернет в течение нескольких дней. мой вопрос по вашей рекомендации. –

+0

@mimin, я отредактировал ответ, надеюсь, что это поможет. , , – Mike

+0

спасибо.Нет, класс UDP не наследуется от QThread, я перемещаю его только в режиме перемещения QThread moveToThread. Я буду следовать вашим предложениям. –

Смежные вопросы