Search code examples
socketsc++builderindy

Indy UDP Server very low speed


I'm using RAD Studio 10.2 with Indy 10, a component IdUDPServer. To test the speed of UDP connection, I installed the program UDP Test Tool.

My code for checking speed of the UDP server:

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

int readCounter = 0;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    Memo1->Clear();
    TIdSocketHandle *SocketHandle = IdUDPServer1->Bindings->Add();
    SocketHandle->IP = "127.0.0.1";
    SocketHandle->Port = 14014;
    IdUDPServer1->Active = true;
    if (IdUDPServer1->Active == true) {
        Memo1->Lines->Add("Сервер стартовал");
        Button1->Enabled = false;
        Button2->Enabled = true;
    }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
    Memo1->Clear();
    IdUDPServer1->Active = false;
    IdUDPServer1->Bindings->Clear();
    if(IdUDPServer1->Active == false) {
        Memo1->Lines->Add("Сервер остановлен");
        Button1->Enabled = true;
        Button2->Enabled = false;
    }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::IdUDPServer1UDPRead(TIdUDPListenerThread *AThread, const TIdBytes AData,
          TIdSocketHandle *ABinding)
{
    readCounter++;
    Edit1->Text = readCounter;

    char* szBuff = new char[AData.Length];
    memset(szBuff, 0, AData.Length);
    BytesToRaw(AData, szBuff, AData.Length);
    Memo1->Lines->Add(ToHex(AData));
    Memo1->Clear();
    delete szBuff;
}
//---------------------------------------------------------------------------

I use the readCounter in the method IdUDPServer1UDPRead for later comparison with the number of packets sent via the downloaded test utility, but the counters do not match. It can be seen that the component IdUDPServer is very late.

screenshot

What could be the problem?


Solution

  • The TIdUDPServer::ThreadedEvent property is false by default. That means every time the server receives a data packet, your OnUDPRead event handler is synced with the main UI thread synchronously via TThread::Synchronize(). That will, of course, slow down the server's overall performance.

    To make your server react to the UDP packets more quickly, you need to make your OnUDPRead event handler return control to TIdUDPServer more quickly. That means setting ThreadedEvent to true to skip the TThread::Synchronize() call, do not access the UI at all (or, at least, update the UI asynchronously using TThread::Queue() or TIdNotify), and remove any useless code that is slowing down your handler (you are allocating and filling a char[] buffer that is not being used for anything at all, so that is just wasted overhead).

    For example, if you are using a Clang-based compiler:

    // make sure ThreadedEvent=true...
    void __fastcall TForm1::IdUDPServer1UDPRead(TIdUDPListenerThread *AThread, const TIdBytes AData, TIdSocketHandle *ABinding)
    {
        ++readCounter;
        TThread::Queue(nullptr,
            [=]() {
                Edit1->Text = readCounter;
                Memo1->Lines->Add(ToHex(AData));
            }
        );
    }
    

    Otherwise, if you are using a classic compiler:

    // See "Handling Anonymous Method Types in C++":
    // http://docwiki.embarcadero.com/RADStudio/en/How_to_Handle_Delphi_Anonymous_Methods_in_C%2B%2B
    
    class TMyQueueProc : public TCppInterfacedObject<TThreadProcedure>
    {
    private:
        int m_counter;
        TIdBytes m_bytes;
    
    public:
        TMyQueueProc(int ACounter, const TIdBytes &AData) : m_counter(ACounter), m_bytes(AData) {}
        INTFOBJECT_IMPL_IUNKNOWN(TInterfacedObject);
    
        void __fastcall Invoke()
        {
            Form1->Edit1->Text = m_counter;
            Form1->Memo1->Lines->Add(ToHex(m_bytes));
        }
    };
    
    // make sure ThreadedEvent=true...
    void __fastcall TForm1::IdUDPServer1UDPRead(TIdUDPListenerThread *AThread, const TIdBytes AData, TIdSocketHandle *ABinding)
    {
        ++readCounter;
        TThread::Queue(NULL, _di_TThreadProcedure(new TMyQueueProc(readCounter, AData)));
    }
    

    Or:

    #include <IdSync.hpp>
    
    class TMyNotify : public TIdNotify
    {
    private:
        int m_counter;
        TIdBytes m_bytes;
    
    protected:
        void __fastcall DoNotify()
        {
            Form1->Edit1->Text = m_counter;
            Form1->Memo1->Lines->Add(ToHex(m_bytes));
        }
    
    public:
        TMyNotify(int ACounter, const TIdBytes &AData) : TIdNotify(), m_counter(ACounter), m_bytes(AData) {}
     };
    
    // make sure ThreadedEvent=true...
    void __fastcall TForm1::IdUDPServer1UDPRead(TIdUDPListenerThread *AThread, const TIdBytes AData, TIdSocketHandle *ABinding)
    {
        ++readCounter;
        (new TMyNotify(readCounter, AData))->Notify();
    }