Я разрабатываю так называемое приложение «фидер» на C ++ Builder XE2, в котором используются 2 встроенных компонента Indy — TIdTCPClient и TIdTCPSever. TIdTCPClient используется для получения данных из одного источника, формирует строковое сообщение, а затем с использованием TIdTCPS всякий раз, когда это строковое сообщение отправляется всем клиентским приложениям. Для ретрансляции данных я использую следующую функцию (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 иногда блокирует программу не помогает.
LockList()
должен быть за пределами try
блок.
Contexts
list внутренне использует критический раздел, поэтому перенос этого кода в собственный критический раздел является излишним.
Единственный способ LockList()
Блокировка может происходить, если другой поток уже получил блокировку и не снял ее, либо потому, что он занят использованием списка, либо, скорее всего, он вышел из строя и не снял блокировку.
Не звони Connected()
или же WriteBufferFlush()
в этом коде. Вы не используете буферизацию записи, а вызываете Connected()
снаружи TIdTCPServer
события вызовут гоночные условия на InpuBuffer
соединения, вмешиваясь в поток сервера, который управляет этим соединением, что может привести к сбоям, взаимоблокировкам, поврежденным входящим данным и т. д. Просто позвоните Write()
сам по себе, и пусть он выдаст исключение, если сокет был отключен.
То, что вы показали, является небезопасным способом реализации TCP-вещания с TIdTCPServer
в общем. Вместо этого вы должны реализовать потокобезопасную исходящую очередь для каждого клиента и позволить 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...
}
Других решений пока нет …