向 TIdTCPClient 发送文本时如何修复 Indy TIdTCPServer 冻结?

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

无法从 TIdTCPServerTIdTCPClient 发送文本,服务器挂起(无响应),它只是在尝试向 TIdTCPClient 发送文本时冻结客户。

我最近开始使用 Indy TIdTCPClient 和 TIdTCPServer,客户端应用程序在发送和接收来自服务器表单的响应时运行良好, 然后问题是服务器不发送数据,当我尝试像 Indy 创建者在他们的文档中提供的那样发送数据时,它只是冻结然后停止响应(崩溃):(,奇怪的是服务器发回执行事件的响应,而不是用我的发送函数发送数据,所以这是我使用的代码:

服务器执行事件:

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
}

服务器发送函数:

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.

附加说明: 当客户端断开连接时,TIdTCPServer 甚至从它的 OnExecute 事件中停止发送文本!

更新:

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 );
    }
}

服务器发送函数:

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();
    }
}

客户端接收函数:

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

这个函数是 SynchronizedTThread.

这就是我使用TMemoryStream发送一个TBitmap的方式:

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;
    }
}

TIdTCPServer 是一个多线程组件,您没有考虑 属性。

服务器的各种事件在内部工作线程的上下文中触发,而不是在主 UI 线程的上下文中触发。您的 OnExecute 代码在访问 MessageDisplay1 时未与主 UI 线程同步,这可能会导致各种问题,包括但不限于死锁。您必须与主 UI 线程同步,例如 TThread::Synchronize()TThread::Queue()。例如:

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!"));
}

此外,您有 2 个线程(调用 itsSendMessage() 的线程和 OnExecute 线程)彼此不同步,因此它们 可能 同时向同一客户端写入文本,相互重叠文本,从而破坏您的通信。从服务器向客户端发送 未经请求的 消息时,我 通常 建议(视情况而定)将消息排队并让客户端线程的 OnExecute 代码决定何时发送队列是安全的。这样做的另一个原因是为了避免在一个客户端被阻止时出现死锁,您不想阻止对其他客户端的访问。尽可能多地在客户自己的 OnExecute 活动中针对每个客户开展工作。例如:

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();
    }
}