Как исправить зависание Indy TIdTCPServer при отправке текста в TIdTCPClient? - PullRequest
0 голосов
/ 07 февраля 2019

Невозможно отправить текст с TIdTCPServer На TIdTCPClient , сервер завис (не отвечает), он просто зависает при попытке отправить текст клиенту.

IНачато недавно с использованием Indy TIdTCPClient и TIdTCPServer, у меня клиентское приложение работает хорошо при отправке и получении ответа от серверной формы, затем проблема с сервера, он не отправляет данные, и когда я пытаюсь отправить данные, как создатели Indy, предоставленные вих Doc, он просто зависает, а затем перестает отвечать (вылетает) :(, странно то, что сервер отправляет ответ на событие Execute, а не отправляет данные с моей функцией отправки, поэтому вот мой код, который я использую:

Событие выполнения сервера:

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.

Вот как я отправляю a 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 голосов
/ 07 февраля 2019

TIdTCPServer - это многопоточный компонент, который вы не учитываете.

Различные события сервера генерируются в контексте внутренних рабочих потоков, а не в контексте основного потока пользовательского интерфейса.Ваш код OnExecute не синхронизируется с основным потоком пользовательского интерфейса при доступе к MessageDisplay1, что может вызвать все виды проблем, включая, но не ограничиваясь, взаимоблокировки.Вы ДОЛЖНЫ синхронизироваться с основным потоком пользовательского интерфейса, например, с 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();
    }
}
...