我们有一个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。
答案 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();
}
}