TIdTCPServer OnExecute не запускается для некоторых сообщений, если в окне не указана мышь - PullRequest
0 голосов
/ 22 мая 2019

У нас есть приложение для Windows, в которое мы добавляем интерфейс сокетов для удаленной настройки и обработки данных. Объект TIdTCPServer получает сообщения в событии OnExecute. Однако для некоторых сообщений событие OnExecute не вызывается, если курсор не перемещен в главном окне.

ОБНОВЛЕНИЕ: С гораздо большим количеством экспериментов, кажется ли более случайным, обрабатывается ли сообщение немедленно, или после долгой паузы, или нет вообще. Во всех случаях перемещение курсора приводит к немедленной обработке сообщения. Тем не менее, оно не относится к сообщению или порядку сообщений.

Обновлены списки исходного кода: вот обработчик OnExecute:

void __fastcall TSigToolForm::IdTCPServer1Execute(TIdContext *AContext)
{
    TIdBytes buffer;
    if (ReceiveBuffer(AContext, buffer))
    {
        try
        {
            try
            {
                msg = &buffer[0];                   // msg is class member
                TThread::Synchronize(0, mProcess);  // Doesn't return until mProcess finishes
                buffer = IPOK().toByteArray();      // Ack
                SendBuffer(AContext, buffer);
            }
            catch (const std::exception & ex)
            {
                buffer = IPFailCommand(ex.what()).toByteArray();
                SendBuffer(AContext, buffer);
            }
            catch (const Exception & ex)
            {
                buffer = IPFailCommand(toStdString(ex.Message)).toByteArray();
                SendBuffer(AContext, buffer);
            }
            catch (const EIdException & ex)
            {
                throw;                              // Let Indy have it
            }
        }
        __finally
        {
            msg = 0;
        }
    }
}

Функция mProcess и функция processMessage, которую она вызывает. Я удалил все сообщения, кроме одного, который обрабатывает processMessage:

void __fastcall TSigToolForm::mProcess()
{
    if (msg) processMessage(msg);
}

void TSigToolForm::processMessage(byte * message)
{
    CSLock lock(cs);            // RAII class, cs is TCriticalSection
    try
    {
        IPCommand cmd(message);
        switch (cmd.ID)
        {
            case IPCommand::SET_CAD :
            {
                setObjectCad(cmd);
                break;
            }
        }
    }
    catch(const std::exception & ex)
    {
        ShowMessage(ex.what());
    }
    catch (const EIdException & ex)
    {
        throw;
    }
    catch (...)
    {
        ShowMessage("Exception in processMessage");
    }
}

Функции ReceiveBuffer и SendBuffer:


bool ReceiveBuffer(TIdTCPClient * aClient, TIdBytes & ABuffer)
{
    return ReceiveBuffer(aClient->IOHandler, ABuffer);
}


bool ReceiveBuffer(TIdContext * AContext, TIdBytes & ABuffer)
{
    return ReceiveBuffer(AContext->Connection->IOHandler, ABuffer);
}


bool ReceiveBuffer(TIdIOHandler * IO, TIdBytes & ABuffer)
{
    CSLock lock(cs);
    try
    {
        long sz = IO->ReadLongInt();
        IO->ReadBytes(ABuffer, sz, false);
        return true;
    }
    catch (const EIdException & ex)
    {
        throw;
    }
    return false;
}


bool SendBuffer(TIdIOHandler * IO, const TIdBytes & ABuffer)
{
    CSLock lock(cs);
    try
    {
        IO->WriteBufferOpen();
        try
        {
            IO->Write(ABuffer.Length);
            IO->Write(ABuffer);
            IO->WriteBufferClose();
        }
        catch(const Exception &)
        {
            IO->WriteBufferCancel();
            throw;
        }
    }
    catch(const EIdException &)
    {
        throw;
    }
    catch (...)
    {
        return false;
    }
    return true;
}


bool SendBuffer(TIdContext * AContext, const TIdBytes & ABuffer)
{
    return SendBuffer(AContext->Connection->IOHandler, ABuffer);
}

bool SendBuffer(TIdTCPClient * aClient, const TIdBytes & aBuffer)
{
    return SendBuffer(aClient->IOHandler, aBuffer);
}

Для тестирования у меня есть отдельная программа, которая создает и отправляет различные сообщения, используя TIdTCPClient и те же функции буфера отправки / получения. Это единственное соединение с программой сервера. Вот пример:

void TForm16::setPortAndConnect()
{
    IdTCPClient1->Port = bdePort->IntValue();
    IdTCPClient1->Host = editHost->Text;
    IdTCPClient1->Connect();
}

void TForm16::sendCommandToSVST(const TIdBytes & buffer)
{
    try
    {
        setPortAndConnect();
        if (SendBuffer(IdTCPClient1, buffer))
        {
            TIdBytes recv;
            //
            // Read the response
            if (ReceiveBuffer(IdTCPClient1, recv))
            {
                IPCommand response = IPCommand::fromByteArray(recv);
            }
        }
    }
    __finally
    {
        IdTCPClient1->Disconnect();
    }
}

bdePort является внутренним производным от TEdit, который имеет дело с числовым вводом. Я уверен, что сами данные верны. Это заставляет сервер отвечать, и это проблема сейчас.

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

Это C ++ Builder 10.1 update 1, использующий классический компилятор.

1 Ответ

1 голос
/ 23 мая 2019

Однако для некоторых сообщений событие OnExecute не вызывается, если курсор не перемещается в главном окне.

TIdTCPServer - многопоточный компонент, OnExecute событие запускается в рабочем потоке в непрерывном цикле в течение всего времени жизни соединения с сокетом.Таким образом, ЕДИНСТВЕННЫЙ способ блокирования до тех пор, пока не будет обнаружена активность мыши, - это если ваш код OnExecute синхронизируется с основным потоком пользовательского интерфейса, а основной поток пользовательского интерфейса блокируется до получения сообщений окна.

Вкод, который вы показали, единственные места, где ваш код OnExecute может быть заблокирован, это вызовы ReceiveBuffer(), mProcess() и SendBuffer().Убедитесь, что они все потокобезопасны.Вы не показывали код для любого из этих методов или код для основного потока пользовательского интерфейса, но mProcess() вызывается через TThread::Synchronize(), поэтому начните с этого и убедитесь, что ваш основной поток пользовательского интерфейса не блокирует mProcess()пока он пытается обработать сообщение сокета.

Кстати, вы перехватываете только исключения на основе STL (полученные из std::exception), но вы полностью игнорируете исключения на основе RTL (полученные из System::Sysutils::Exception),И в случае основанных на Indy исключений (которые получены из EIdException, что само по себе происходит из System::Sysutils::Exception), НЕ глотайте их!Если вы перехватываете исключение Indy, перезапустите его и дайте TIdTCPServer обработать его, иначе его потоки не смогут обнаружить разъединения сокетов и очистить их должным образом (если вы вручную не вызовете AContext->Connection->Disconnect() в своем коде).

Не знаю версию Indy, что бы ни приходило с компилятором.

Вы можете узнать версию Indy:

  • ищем Indy в поле «About» среды IDE

  • , щелкнув правой кнопкой мыши любой компонент Indy в конструкторе форм во время разработки.

  • чтение свойства Version любого компонента Indy во время выполнения.

ОБНОВЛЕНИЕ: Почему вы используете критический раздел вокруг всего?Тебе это не нужно.

Вы читаете / записываете сокет клиента только из одного потока (тот, который запускает событие OnExecute).Даже если вы читаете в одном потоке и пишете в другом, это безопасно делать с сокетами, не устанавливая замок вокруг IOHandler.Таким образом, вам вообще не нужна блокировка этих операций IOHandler.

И ваш метод mProcess() уже сериализован TThread::Synchronize(), поэтому он будет работать только в основном потоке пользовательского интерфейса.Если несколько потоков клиентов хотят одновременно вызывать mProcess(), Synchronize() гарантирует, что он запускается только по одному за раз.Так что для этого вам тоже не нужен замок.Однако использование ShowMessage() внутри mProcess() проблематично, поскольку он запускает вторичный цикл обработки сообщений, который позволяет выполнять ожидающие запросы Synchronize(), пока mProcess() все еще работает, так что вы можете получить несколько mProcess() звонки перекрывают друг друга.Вы не должны делать ничего внутри синхронизируемого метода, который может вызвать обработку сообщений окна.Если синхронизированный метод генерирует исключение, вы не должны пытаться его перехватить.Synchronize() ловит исключения и перебрасывает их в контексте потока, который вызвал Synchronize(), и у вас уже есть обработчики исключений в вашем OnExecute коде.

Единственное место, где я вижу, где вы должны использовать любыевид блокировки, если он вообще есть, будет внутри setObjectCad(), но только если ему необходим доступ к данным, к которым могут обращаться несколько потоков одновременно.

С учетом сказанного попробуйте что-то ещекак это вместо этого:

void ReceiveBuffer(TIdTCPClient * aClient, TIdBytes & ABuffer)
{
    ReceiveBuffer(aClient->IOHandler, ABuffer);
}

bool ReceiveBuffer(TIdContext * AContext, TIdBytes & ABuffer)
{
    ReceiveBuffer(AContext->Connection->IOHandler, ABuffer);
}

void ReceiveBuffer(TIdIOHandler * IO, TIdBytes & ABuffer)
{
    long sz = IO->ReadLongInt();
    IO->ReadBytes(ABuffer, sz, false);
}

void SendBuffer(TIdIOHandler * IO, const TIdBytes & ABuffer)
{
    IO->WriteBufferOpen();
    try
    {
        IO->Write(ABuffer.Length);
        IO->Write(ABuffer);
        IO->WriteBufferClose();
    }
    catch(const Exception &)
    {
        IO->WriteBufferCancel();
        throw;
    }
}

void SendBuffer(TIdContext * AContext, const TIdBytes & ABuffer)
{
    SendBuffer(AContext->Connection->IOHandler, ABuffer);
}

void SendBuffer(TIdTCPClient * aClient, const TIdBytes & aBuffer)
{
    SendBuffer(aClient->IOHandler, aBuffer);
}
void __fastcall TSigToolForm::IdTCPServer1Execute(TIdContext *AContext)
{
    TIdBytes buffer;
    ReceiveBuffer(AContext, buffer);

    try
    {
        msg = &buffer[0];                   // msg is class member
        TThread::Synchronize(0, mProcess);  // Doesn't return until mProcess finishes
        buffer = IPOK().toByteArray();      // Ack
        SendBuffer(AContext, buffer);
    }
    catch (const std::exception & ex)
    {
        buffer = IPFailCommand(ex.what()).toByteArray();
        SendBuffer(AContext, buffer);
    }
    catch (const Exception & ex)
    {
        buffer = IPFailCommand(toStdString(ex.Message)).toByteArray();
        SendBuffer(AContext, buffer);
        if (dynamic_cast<const EIdException *>(&ex))
            throw;
    }
    catch (...)
    {
        buffer = IPFailCommand("Unknown exception").toByteArray();
        SendBuffer(AContext, buffer);
    }
}

void __fastcall TSigToolForm::mProcess()
{
    if (msg) processMessage(msg);
}

void TSigToolForm::processMessage(byte * message)
{
    IPCommand cmd(message);
    switch (cmd.ID)
    {
        case IPCommand::SET_CAD :
        {
            setObjectCad(cmd);
            break;
        }
    }
}

void TSigToolForm::setObjectCad(const IPCommand &cmd)
{
    // here is where you should be using CSLock, if at all...
}
void TForm16::setPortAndConnect()
{
    IdTCPClient1->Port = bdePort->IntValue();
    IdTCPClient1->Host = editHost->Text;
    IdTCPClient1->Connect();
}

void TForm16::sendCommandToSVST(const TIdBytes & buffer)
{
    setPortAndConnect();
    try
    {
        // Send the command
        SendBuffer(IdTCPClient1, buffer);

        // Read the response
        TIdBytes recv;
        ReceiveBuffer(IdTCPClient1, recv);
        IPCommand response = IPCommand::fromByteArray(recv);
    }
    __finally
    {
        IdTCPClient1->Disconnect();
    }
}
...