发送文本到TIdTCPClient时如何解决Indy TIdTCPServer冻结?

时间:2019-02-07 07:25:58

标签: indy10 c++builder-2010

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

我最近开始使用Indy TIdTCPClient和TIdTCPServer,当从服务器表单发送和接收响应时,我的客户端应用程序运行良好, 然后问题是来自服务器,它不发送数据,当我尝试发送数据(如其文档中提供的Indy创建者)时,它只是冻结,然后停止响应(崩溃):(,奇怪的是服务器发回了Execute Event上的响应,而不是使用send函数发送数据,因此这是我使用的代码:

服务器执行事件:

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

此功能是Synchronized中的TThread

这就是我使用TBitmap发送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;
    }
}

1 个答案:

答案 0 :(得分:0)

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