简体   繁体   中英

How did TIdTCPServer multicast to ALL Clients in A 60Hz timer?

I am a newbie working with Indy. This is my first time posting a question here.

My project has to send data to all clients at 60Hz. I am using TIdTCPServer for this, and to monitor keep-alives. My tool is very old, running on WinXP, using C++Builder 6 and Indy 8. There is a potential timeout issue, does anyone have a good thought how to handle it?

Here is my code:

Server Side

typedef struct
{
    AnsiString PeerIP;     //{ Cleint IP address }
    AnsiString HostName;   //{ Hostname }
    int Id;        // {Cleint ID}
} TClient;


// This is Multimedia timer callback function, on 60Hz

void CALLBACK mmTimerProc(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
    DWORD T1, T2;
    TfmMain *pMain = (TfmMain *)dwUser;
    int Index;
    double dT;
    TClient *pClient;

    if (pMain->IsClosing) return;


    if (pMain->Shutdown)
    {
        return;
    }


    pMain->UpdateServer1Data();

    TList *pList = pMain->IdTCPServer1->Threads->LockList();
    try
    {
        for(int X = 0; X < pList->Count; X++)
        {
            if (!pMain->IsClosing)
            {
                TIdPeerThread *AThread = (TIdPeerThread *)pList->Items[X];

                if(AThread != NULL)
                {
                    pClient = (TClient *)AThread->Data;
                    try
                    {
                        if(!AThread->Connection->ClosedGracefully)
                        {
                            // Send data to ALL Clients
                            AThread->Connection->WriteBuffer(&pMain->Data2Client, sizeof(pMain->Data2Client), true);
                        }
                    }
                    catch(Exception &E)
                    {
                        if(!AThread->Stopped)
                        {
                            AThread->Stop();
                            AThread->Connection->Disconnect();
                        }
                    }
                }
            }
        }
    }
    __finally
    {
        pMain->IdTCPServer1->Threads->UnlockList();
    }

    // Shutdown computer or close application

    if(pMain->SimIos.Status.iSimStatus == 11)
    {
        pMain->Shutdown = true;
        pMain->CloseApp();
    }
}


void __fastcall TfmMain::IdTCPServer1Connect(TIdPeerThread *AThread)
{
    TClient *pClient = NULL;
    AnsiString ABuffer, text;
    AnsiString PeerIP   = AThread->Connection->Binding->PeerIP;
    TDateTime TimeConnected = Now();


    ABuffer = AThread->Connection->ReadLn();
    if((ABuffer.Pos("TT") == 0) && (ABuffer.Pos("IG") == 0) && (ABuffer.Pos("RR") == 0))
    {
        text = AnsiString().sprintf("1>>> Unknown(%s) on %s connected illegal!...",
                            PeerIP, DateTimeToStr(TimeConnected));
        WriteMsg(text);
        AThread->Connection->Disconnect();
        return;
    }

    if(ABuffer.Pos("IG") != 0)
    {
        pClient = new TClient;
        pClient->PeerIP     = PeerIP;
        pClient->HostName   = Clients[eIG];
        pClient->Id         = eIG;

        AThread->Data = (TObject *)pClient;
        AThread->FreeOnTerminate = true;

        // Report client is on line
    }

    text = AnsiString().sprintf("1>>>%s(%s) on %s on line!...",
                        pClient->HostName, PeerIP, DateTimeToStr(TimeConnected));
    WriteMsg(text);    
}
//---------------------------------------------------------------------------

void __fastcall TfmMain::IdTCPServer1Disconnect(TIdPeerThread *AThread)
{
    TClient *pClient = NULL;
    AnsiString Msg;


    if (IsClosing) return;


    pClient = (TClient *)AThread->Data;

    if(pClient->Id == 1)
    {
        // Report client is off line
        Msg = AnsiString().sprintf("1>>>%s(%s) on %s off line...",
        pClient->HostName, pClient->PeerIP, DateTimeToStr(Now()));
        WriteMsg(Msg);
    }


    delete pClient;
    AThread->Data = NULL;
    AThread->Terminate();

    try
    {
        IdTCPServer1->Threads->LockList()->Remove(AThread);
    }
    __finally
    {
        IdTCPServer1->Threads->UnlockList();
    }

}
//---------------------------------------------------------------------------

void __fastcall TfmMain::IdTCPServer1Execute(TIdPeerThread *AThread)
{
    TClient *pClient;


    if (!AThread->Terminated && !IsClosing)
    {
        pClient = (TClient *)AThread->Data;
        if((pClient != NULL) && (pClient->Id != 0))
        {
            try
            {
                if(pClient->Id == 1)
                {
                    // Report client still alive
                }
            }
            catch(Exception &E)
            {
                if (!IsClosing)
                {
                    if(!AThread->Stopped)
                    {
                        AThread->Stop();
                        AThread->Connection->Disconnect();
                    }
                }
            }
        }
    }
}

Client side

void __fastcall TSocketThread::Execute()
{
    unsigned long ulCheckSum;
    SIM_SVR1_ACK_STRUCT   Ack2Svr;
    SIM_SVR_DATA   DataFromSvr;
    int Counter;


    memset(&DataFromSvr, 0, sizeof(DataFromSvr));
    memset(&Ack2Svr,  0, sizeof(Ack2Svr));
    Counter = 0;

    // fetch and process commands until the connection or thread is terminated
    while (!this->Terminated && FSocket->Connected())
    {
        try
        {
            // recieve data from server
            FSocket->ReadBuffer(&DataFromSvr, sizeof(DataFromSvr));

            // check CRC
            ulCheckSum = CRC_32((unsigned char*)&DataFromSvr.SimData, sizeof(DataFromSvr.SimData));

            if (ulCheckSum == DataFromSvr.uiCheckSum)
            {
                FSmIpcUtil->Writeto(&DataFromSvr.SimData, DATA_OFFSET, sizeof(DataFromSvr.SimData));
            }

            else
            {
                // counter to record error  
                Synchronize(UpdateCaption);
            }

            // read return from local SM
            FSmIpcUtil->Readfrom(&Ack2Svr, ACK_OFFSET, sizeof(Ack2Svr));

            FSocket->WriteBuffer(&Ack2Svr, sizeof(Ack2Svr));

            if (DataFromSvr.SimData.SimIgTgt.Acdata.iSimStatus == 11)
            {
                Terminate();
                FSocket->Disconnect();

                PostMessage(Application->Handle, WM_SHUTDOWN, 0, 0);

                Sleep(500);
            }
        }

        catch (EIdException& E)
        {
            this->Terminate();
            FSocket->Disconnect();
        }
    }
}

There are several issues with your code.

A multimedia timer callback is very restricted in what it is allowed to do:

Applications should not call any system-defined functions from inside a callback function, except for PostMessage , timeGetSystemTime , timeGetTime , timeSetEvent , timeKillEvent , midiOutShortMsg , midiOutLongMsg , and OutputDebugString .

If transmission speed is important, don't have the timer callback do the broadcasting at all. Save the data somewhere safe, and then have each TIdTCPServer thread grab the latest data on its own time. This also keeps each connection thread isolated so one connection cannot affect any other connection if problems occur.

DO NOT set the TIdPeerThread::FreeOnTerminate to true, DO NOT call TIdPeerThread::Stop() , DO NOT manually remove threads from the TIdTCPServer::Threads property. You do not own the threads, TIdTCPServer does, and it will manage them for you. If you want to stop a given thread, closing the thread's socket is all you need to do. But by moving all of the sending logic into OnExecute where it belongs, you can let TIdTCPServer handle any errors and close the socket for you.

Your OnConnect event handler is setting AThread->Data only if an IG client connects, but your OnConnect and OnDisconnect handlers are not checking for that condition before attempting to access the TClient object.

Your OnDisconnect event handler is leaking memory if IsClosing is true. And it is calling Threads->UnlockList() without calling Threads->LockList() first. Attempting to unlock the list when it is not locked by the calling thread will cause errors and deadlocks.

Try something more like this:

struct TClient
{
    AnsiString PeerIP;     //{ Client IP address }
    AnsiString HostName;   //{ Hostname }
    int Id;                //{ Client ID }
};

void CALLBACK mmTimerProc(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
    TfmMain *pMain = (TfmMain *)dwUser;

    if (pMain->IsClosing || pMain->Shutdown) return;

    pMain->UpdateServer1Data();    
    // make sure pMain->Data2Client is thread-safe...
    // set a signal that Data2Client has been updated...

    // Shutdown computer or close application

    if (pMain->SimIos.Status.iSimStatus == 11)
    {
        pMain->Shutdown = true;
        pMain->CloseApp();
    }
}

void __fastcall TfmMain::IdTCPServer1Connect(TIdPeerThread *AThread)
{
    TClient *pClient;
    AnsiString ABuffer, text;
    AnsiString PeerIP = AThread->Connection->Binding->PeerIP;
    TDateTime TimeConnected = Now();

    ABuffer = AThread->Connection->ReadLn();
    if ((ABuffer.Pos("TT") == 0) && (ABuffer.Pos("IG") == 0) && (ABuffer.Pos("RR") == 0))
    {
        text = AnsiString().sprintf("1>>> Unknown(%s) on %s connected illegal!...", PeerIP.c_str(), DateTimeToStr(TimeConnected).c_str());
        WriteMsg(text);
        AThread->Connection->Disconnect();
        return;
    }

    pClient = new TClient;
    pClient->PeerIP = PeerIP;

    if (ABuffer.Pos("IG") != 0)
    {
        pClient->HostName   = Clients[eIG];
        pClient->Id         = eIG;
    }
    else
        pClient->Id         = 0;

    AThread->Data = (TObject *)pClient;

    // Report client is on line

    text = AnsiString().sprintf("1>>>%s(%s) on %s on line!...", pClient->HostName.c_str(), PeerIP.c_str(), DateTimeToStr(TimeConnected).c_str());
    WriteMsg(text);    
}

void __fastcall TfmMain::IdTCPServer1Disconnect(TIdPeerThread *AThread)
{
    TClient *pClient = (TClient *)AThread->Data;
    AnsiString Msg;

    AThread->Data = NULL;

    if (pClient)
    {
        // Report client is off line
        Msg = AnsiString().sprintf("1>>>%s(%s) on %s off line...",
            pClient->HostName.c_str(), pClient->PeerIP.c_str(), DateTimeToStr(Now()).c_str());
        WriteMsg(Msg);

        delete pClient;
    }
}

void __fastcall TfmMain::IdTCPServer1Execute(TIdPeerThread *AThread)
{
    TClient *pClient;

    if (IsClosing) return;

    // make sure pMain->Data2Client is thread-safe...
    if (Data2Client has been updated since last event)
    {
        AThread->Connection->WriteBuffer(&pMain->Data2Client, sizeof(pMain->Data2Client), true);
    }

    pClient = (TClient *)AThread->Data;
    // Report client still alive
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM