indy10c++builder-2010

How To Fix Indy TIdTCPServer Freezing When Sending Text To TIdTCPClient?


Cannot send text from TIdTCPServer To TIdTCPClient, the server hanging (Not Responding), it just freezes when trying to send text to the client.

I Started Recently using Indy TIdTCPClient and TIdTCPServer, i have the client application working well when sending and receiving response from the server form, then issue is from the server it doesn't send data, and when i try to send data like Indy creators provided in their Doc, it just freezes and then stops responding (crashes) :(, the weird thing is that the server sends back the response on Execute Event, and not sending data with my send function, so here is my code that i use:

Server Execute Event:

void __fastcall TServerMain::IdTCPServer1Execute(TIdContext *AContext)
{
   UnicodeString uMessage;

   uMessage      = AContext->Connection->IOHandler->ReadLn();
   MessageDisplay1->Lines->Add(uMessage);
   AContext->Connection->IOHandler->WriteLn("Response OK!"); // i can receive the response from the client
}

Server Send Function:

void TServerMain::itsSendMessage(TIdTCPServer *itsName, UnicodeString uMessage) {
   TIdContextList *Clients;
   TIdContext *icContext;
   if ( uMessage.Length() != 0 && itsName->Active ) {
     Clients = itsName->Contexts->LockList();
     for (int i = 0; i < Clients->Count; i++) {
        icContext = (TIdContext*)Clients->Items[i];
        icContext->Connection->IOHandler->WriteLn(uMessage);
     }
   itsName->Contexts->UnlockList();
   }
 } // this function doesn't send text to the clients however, it just hangs the application for ever.

Additional Note: The TIdTCPServer stops sending text even from it's OnExecute event when a client is disconnects!

UPDATE:

void __fastcall TMyContext::AddToQueue(TStream *AStream)
{
    TStringList *queue = this->FQueue->Lock();
    try {
        queue->AddObject("", AStream);
        this->FMessageInQueue = true;
    }
    __finally
    {
        this->FQueue->Unlock();
    }
}

void __fastcall TMyContext::CheckQueue()
{
    if ( !this->FMessageInQueue )
        return;

    std::unique_ptr<TStringList> temp(new TStringList);
    TStringList *queue = this->FQueue->Lock();
    try {
        temp->OwnsObjects = true;
        temp->Assign(queue);
        queue->Clear();
        this->FMessageInQueue = false;
    }
    __finally
    {
        this->FQueue->Unlock();
    }
    for (int i = 0; i < temp->Count; i++) {
        this->Connection->IOHandler->Write( static_cast<TStream*>(temp->Objects[i]), static_cast<TStream*>(temp->Objects[i])->Size, true );
    }
}

Server Send Function:

void __fastcall TServerMain::IdSendMessage(TIdTCPServer *IdTCPServer, TStream *AStream)
{
    if ( !IdTCPServer->Active )
        return;

    TIdContextList *Clients = IdTCPServer->Contexts->LockList();
    try {
        for (int i = 0; i < Clients->Count; i++) {
            static_cast<TMyContext*>(static_cast<TIdContext*>(Clients->Items[i]))->AddToQueue(AStream);
        }
    }
    __finally
    {
        IdTCPServer->Contexts->UnlockList();
    }
}

Client Receive Function:

void __fastcall TReadingThread::Receive() {
    TMemoryStream * ms = new TMemoryStream();
    this->IdTCPClient1->IOHandler->ReadStream(ms);
    ms->Position = 0;
    ClientMain->Image1->Picture->Bitmap->LoadFromStream(ms);
    delete ms;
}

This function is Synchronized in a TThread.

This is how i send a TBitmap using TMemoryStream:

void __fastcall TServerMain::CaptureDesktop()
{
    // Capture Desktop Canvas
    HDC hdcDesktop;
    TBitmap *bmpCapture    = new TBitmap();
    TMemoryStream *Stream  = new TMemoryStream();
    try {
        bmpCapture->Width  = Screen->Width;
        bmpCapture->Height = Screen->Height;
        hdcDesktop = GetDC(GetDesktopWindow());
        BitBlt(bmpCapture->Canvas->Handle, 0,0,Screen->Width, Screen->Height, hdcDesktop, 0,0, SRCCOPY);
        bmpCapture->SaveToStream(Stream);
        Stream->Position = 0;
        IdSendMessage(IdTCPServer1, Stream);
    }
    __finally
    {
        ReleaseDC(GetDesktopWindow(), hdcDesktop);
        delete bmpCapture;
        delete Stream;
    }
}

Solution

  • TIdTCPServer is a multi-threaded component, which you are not accounting for propery.

    The server's various events are fired in the context of internal worker threads, not in the context the main UI thread. Your OnExecute code is not syncing with the main UI thread when accessing MessageDisplay1, which can cause all kinds of problems, including but not limited to deadlocks. You MUST sync with the main UI thread, such as with TThread::Synchronize() or TThread::Queue(). For example:

    void __fastcall TServerMain::IdTCPServer1Execute(TIdContext *AContext)
    {
        String Message = AContext->Connection->IOHandler->ReadLn();
    
        // see http://docwiki.embarcadero.com/RADStudio/en/How_to_Handle_Delphi_Anonymous_Methods_in_C%2B%2B
        TThread::Queue(nullptr, [](){ MessageDisplay1->Lines->Add(Message); });
    
        AContext->Connection->IOHandler->WriteLn(_D("Response OK!"));
    }
    

    Also, you have 2 threads (whichever thread is calling itsSendMessage(), and the OnExecute thread) that are not syncing with each other, so they can potentially write text to the same client at the same time, overlapping each other's text and thus corrupting your communications. When sending unsolicited messages from the server to a client, I usually recommend (depending on circumstances) that you queue the messages and let the client thread's OnExecute code decide when it is safe to send the queue. Another reason to do this is to avoid deadlocks if one client becomes blocked, you don't want to block access to other clients. Do as much per-client work in the client's own OnExecute event as you can. For example:

    class TMyContext : public TIdServerContext
    {
    private:
        TIdThreadSafeStringList *FQueue;
        bool FMsgInQueue;
    
    public:
        __fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, TIdContextThreadList *AList = nullptr)
            : TIdServerContext(AConnection, AYarn, AList)
        {
            FQueue = new TIdThreadSafeStringList;
        }
    
        __fastcall ~TMyContext()
        {
            delete FQueue;
        }
    
        void AddToQueue(const String &Message)
        {
            TStringList *queue = FQueue->Lock();
            try
            {
                queue->Add(Message);
                FMsgInQueue = true;
            }
            __finally
            {
                FQueue->Unlock();
            }
        }
    
        void CheckQueue()
        {
            if (!FMsgInQueue)
                return;
    
            std::unique_ptr<TStringList> temp(new TStringList);
    
            TStringList *queue = FQueue->Lock();
            try
            {
                temp->Assign(queue);
                queue->Clear();
                FMsgInQueue = false;
            }
            __finally
            {
                FQueue->Unlock();
            }
    
            Connection->IOHandler->Write(temp.get());
        }
    
        bool HasPendingData()
        {
            TIdIOHandler *io = Connection->IOHandler;
    
            bool empty = io->InputBufferIsEmpty();
            if (empty)
            {
                io->CheckForDataOnSource(100);
                io->CheckForDisconnect();
                empty = io->InputBufferIsEmpty();
            }
    
            return !empty;
        }
    };
    
    __fastcall TServerMain::TServerMain(...)
    {
        IdTCPServer1->ContextClass = __classid(TMyContext);
        ...
    }
    
    void __fastcall TServerMain::IdTCPServer1Execute(TIdContext *AContext)
    {
        TMyContext *ctx = static_cast<TMyContext*>(AContext);
    
        ctx->CheckQueue();
    
        if (!ctx->HasPendingData())
            return;
    
        String Message = AContext->Connection->IOHandler->ReadLn();
        TThread::Queue(nullptr, [](){ MessageDisplay1->Lines->Add(Message); });
        AContext->Connection->IOHandler->WriteLn(_D("Response OK!"));
    }
    
    void TServerMain::itsSendMessage(TIdTCPServer *itsName, const String &Message)
    {
        if ( Message.IsEmpty() || !itsName->Active )
            return;
    
        TIdContextList *Clients = itsName->Contexts->LockList();
        try
        {
            for (int i = 0; i < Clients->Count; ++i)
            {
                static_cast<TMyContext*>(static_cast<TIdContext*>(Clients->Items[i]))->AddToQueue(Message);
            }
        }
        __finally
        {
            itsName->Contexts->UnlockList();
        }
    }