【问题标题】:Application hangup on TIdTCPServer Contexts->LockList()TIdTCPServer Contexts->LockList() 上的应用程序挂起
【发布时间】:2015-10-21 09:52:02
【问题描述】:

我正在 C++ Builder XE2 中开发所谓的“feeder”应用程序,它使用 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 的 Remy 解决方案没有帮助。

【问题讨论】:

    标签: c++ deadlock indy10


    【解决方案1】:

    LockList() 需要位于 try 块之外。

    Contexts 列表在内部使用了一个临界区,因此将此代码包装在您自己的临界区中是多余的。

    LockList() 可以阻塞的唯一方法是,如果另一个线程已经获得了锁并且没有释放它,要么是因为它正忙于使用列表,要么它更可能崩溃并且没有释放锁。

    请勿在此代码中调用 Connected()WriteBufferFlush()。您没有使用写缓冲,从TIdTCPServer 事件之外调用Connected() 将导致连接的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...
    }
    

    【讨论】:

    • 非常感谢您睁开眼睛正确处理上下文,没想到这么详细的代码。会尝试并反馈。
    • 我有 10.5.8.0 Indy 版本,它没有定义 TIdContextThreadList。我可以在 TMyContext 构造函数中使用 System::Classes::TThreadList 而不更改代码中的任何其他内容吗?
    • 还有一个问题...我的原始代码允许立即重新翻译(在我的情况下,1-2 毫秒的延迟非常重要),并且使用您提供的代码,我将延迟检查输入缓冲区.在 OnExecute 事件之前,我只是在读取来自客户端的数据,现在我终于明白这可能是死锁的原因。 CheckForDataOnSource 中的参数可以从 100 更改为 1 或 2 没有任何问题吗?
    • 是的,在早期版本中使用TThreadList(参见IdContext.pasTIdContext 构造函数的声明)。至于延迟,是的,您可以减少使用的超时。但是,如果您的 OnExecute 处理程序只需要读取而不需要写入,那么您不需要使用我展示的方法。在一个线程中读取而在另一个线程中写入而不同步对套接字的访问是安全的。我只是不建议在主 UI 线程中使用发送循环。被阻塞的客户端将阻塞整个循环,这就是为什么我将写入移动到 OnExecute 以便发送可以并行运行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-04
    • 2011-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多