我正在C ++ Builder XE2中开发所谓的“馈送器”应用程序,它使用2个内置的Indy组件 - TIdTCPClient和TIdTCPSever。 TIdTCPClient用于接收来自一个源的数据,形成字符串消息,并且与使用TIdTCPSever相比,此字符串消息被发送到所有客户端应用程序。对于数据重新翻译,我使用下一个函数(idEventsServerSocket是TIdTCPSever组件):
void TfrmMainWindow::SendDataToAllClients(String msg) {
TList *ClientsList;
try
{
ClientsList = idEventsServerSocket->Contexts->LockList();
for (int i = 0; i < ClientsList->Count; i++) {
TIdContext *Context = (TIdContext*)ClientsList->Items[i];
bool connected = false;
try {
connected = Context->Connection->Connected();
}
catch (Exception&e) {
continue;
}
if (!connected)
continue;
try {
Context->Connection->IOHandler->WriteLn(msg);
Context->Connection->IOHandler->WriteBufferFlush();
}
catch (Exception&e) {
}
}
}
__finally
{
idEventsServerSocket->Contexts->UnlockList();
}
}
我还想注意这个函数包含在EnterCriticalSection ... LeaveCriticalSection代码部分中,因此应该保证在函数未被执行之前不会发生此函数代码的新入口。对于idEventsServerSocket OnException和OnListenException处理程序已定义并包含空代码。
所以问题是:有时是行
ClientsList = idEventsServerSocket->Contexts->LockList();
导致应用程序挂起。当它发生时没有一般规律,但看起来大多数情况下,当非常频繁地调用函数 SendDataToAllClients 时(例如每10-50毫秒一次)。客户端连接数从30到50不等。 我需要知道的是,有什么办法可以避免这种僵局吗?我有任何一种检查(如TryEnterCriticalSection)? 另外我想承认来自Delphi: TThreadList sometimes lock program的雷米的解决方案没有帮助。
答案 0 :(得分:0)
LockList()
必须位于try
区块之外。
Contexts
列表在内部使用关键部分,因此将此代码包装在您自己的关键部分中是多余的。
LockList()
可以阻止的唯一方法是,如果另一个线程已经获得了锁并且没有释放它,或者因为它正忙于使用该列表,或者更可能是它崩溃并且没有释放锁。 / p>
请勿在此代码中致电Connected()
或WriteBufferFlush()
。您没有使用写缓冲,并且从Connected()
事件之外调用TIdTCPServer
将导致连接的InpuBuffer
上的竞争条件,干扰管理该连接的服务器线程,可能会导致崩溃,死锁,入站数据损坏等。只需单独调用Write()
,如果套接字已断开连接,则抛出异常。
您所展示的是一般使用TIdTCPServer
实施TCP广播的不安全方式。您应该实现每个客户端线程安全的出站队列,并让OnExecute
事件处理实际写入:
#include <IdThreadSafe.hpp>
class TMyContext : public TIdServerContext
{
public:
TIdThreadSafeStringList *Queue;
bool HasMsgsInQueue;
__fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, TIdContextThreadList *AList = NULL)
: TIdServerContext(AConnection, AYarn, AList)
{
Queue = new TIdThreadSafeStringList;
HasMsgsInQueue = false;
}
__fastcall TMyContext()
{
delete Queue;
}
};
__fastcall TfrmMainWindow::TfrmMainWindow(TComponent *Owner)
: TForm(Owner)
{
// set this before activating the server
idEventsServerSocket->ContextClass = __classid(TMyContext);
}
void TfrmMainWindow::SendDataToAllClients(const String &msg)
{
TList *ClientsList = idEventsServerSocket->Contexts->LockList();
try
{
for (int i = 0; i < ClientsList->Count; ++i)
{
TMyContext *Context = (TMyContext*) ClientsList->Items[i];
try
{
TStringList *Queue = Context->Queue->Lock();
try
{
Queue->Add(msg);
Context->HasMsgsInQueue = true;
}
__finally
{
Context->Queue->Unlock();
}
}
catch (const Exception &)
{
}
}
}
__finally
{
idEventsServerSocket->Contexts->UnlockList();
}
}
void __fastcall TfrmMainWindow::idEventsServerSocketExecute(TIdContext *AContext)
{
TMyContext *ctx = (TMyContext*) AContext;
if (ctx->HasMsgsInQueue)
{
TStringList *Msgs = NULL;
try
{
TStringList *Queue = ctx->Queue->Lock();
try
{
Msgs = new TStringList;
Msgs->Assign(Queue);
Queue->Clear();
ctx->HasMsgsInQueue = false;
}
__finally
{
ctx->Queue->Unlock();
}
AContext->Connection->IOHandler->Write(Msgs);
}
__finally
{
delete Msgs;
}
}
if (AContext->Connection->IOHandler->InputBufferIsEmpty())
{
AContext->Connection->IOHandler->CheckForDataOnSource(100);
AContext->Connection->IOHandler->CheckForDisconnect();
if (AContext->Connection->IOHandler->InputBufferIsEmpty())
return;
}
// handle inbound data as needed...
}