除非鼠标在窗口中,否则不会对某些消息触发TIdTCPServer OnExecute

时间:2019-05-22 18:17:40

标签: c++builder indy10

我们有一个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更新1。

1 个答案:

答案 0 :(得分:1)

  

但是,对于某些消息,除非将光标移到主窗口中,否则不会触发OnExecute事件。

TIdTCPServer是一个多线程组件,在套接字连接的整个生命周期中,在连续循环中的工作线程中触发OnExecute事件。因此,只有在您的OnExecute代码与主UI线程同步,并且主UI线程被阻塞直到接收到窗口消息之前,阻塞才被检测到,直到检测到鼠标活动为止。

在显示的代码中,OnExecute代码可能被阻塞的唯一地方是对ReceiveBuffer()mProcess()SendBuffer()的调用。确保它们都是线程安全的。您没有显示任何这些方法的代码,也不显示主UI线程的代码,但是mProcess()是通过TThread::Synchronize()调用的,因此请从该代码开始,并确保您的主UI线程没有尝试处理套接字消息时阻止mProcess()

顺便说一句,您仅捕获基于STL的异常(源自std::exception),但完全忽略了基于RTL的异常(源自System::Sysutils::Exception)。而且,对于基于Indy的异常(它们是从EIdException派生的,它本身是从System::Sysutils::Exception派生的),请不要吞下它们!如果捕获到Indy异常,请重新抛出该异常并让TIdTCPServer处理它,否则其线程将无法检测套接字断开连接并正确清理(除非您在代码中手动调用AContext->Connection->Disconnect()

  

编译器随附的内容都不知道Indy版本。

您可以通过以下方式找到印地文版本:

  • 在IDE的“关于”框中查找Indy

  • 在设计时右键单击“表单设计器”中的任何Indy组件。

  • 在运行时读取任何Indy组件的Version属性。

更新:为什么要使用围绕所有内容的关键部分?不用了

您仅从1个线程(触发OnExecute事件的线程)读取/写入客户端套接字。即使您正在读取一个线程并在另一个线程中进行写入,使用套接字也可以安全地进行操作,而无需在IOHandler周围放置锁。因此,您根本不需要锁定这些IOHandler操作。

并且您的mProcess()方法已经由TThread::Synchronize()进行了序列化,因此它只能在主UI线程中运行。如果多个客户端线程要同时调用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();
    }
}