🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: borland.public.cppbuilder.internet.socket
Article #3292

Re: Indy TCP C/S: Timeout , Concurrent access, Number of threads

#3292
From: Remy Lebeau (TeamB)
Date: Thu, 06 Oct 2005 17:21
222 lines
8652 bytes
"Vladimir Stefanovic" <antivari@po.sbb.co.yu> wrote in message
news:434503e6$1@newsgroups.borland.com...

> How can I set the time in which:
>
>    IdTCPClient->Connect();
>
> ... will be waited to connect.

Since you appear to be using Indy 9, then Connect() has an ATimeout
parameter.

> Now, at my PC it's about 15 seconds, and I want to change that.

Simply change the value of the ATimeout parameter.

> If the separate thread is the only solution, can you please
> show me a piece of code.

TIdTCPClient already runs a thread internally to handle the timeout.

> Is it OK that client is responsible for both opening & closing
> the connection?

Yes.  That is typically how TCP is used in the first place.  Unless the
protocol dictates that the server should be the one who closes the
connection, then the client is usually the one who closes it.  This allows
the client to send multiple requests, and to decide when it is actually done
with the connection.

> Is the property TIdTCPServer::TerminateWaitTime, the
> time when server will anyway close the thread by itself
> (connection with client) which was not closed within the
> agreed time period?

TerminateWaitTime() only has effect when the server is being deactivated.
If there are any existing connections at the time the server is being
deactivated then they are are automatically closed and then the server waits
TerminateWaitTime number of milliseconds for all of the threads to be fully
terminated.  If there are still threads running after TerminateWaitTime
milliseconds have elapsed than an EIdTerminateThreadTimeout exception is
thrown.

> Is the code below acceptable

No.

> In other words is threading prepared for me automatically
> (contrary to TServerSocket where I should spawn the new
> thread from OnGetThread() )?

TIdTCPServer handles the threading for you, yes.

> 5) TServerSocket can stay in 'Active == true' but unable
> to receive new requests in some circumstances.

You are referring to when the listening thread crashes, correct?

> Is that the same at TIdTCPServer?

If an unexpected exception occurs while accepting a new connection, the
server aborts the connection and closes it, then triggers its
OnListenException event, and then continues listening for new connections as
if nothing happened.  The only way for the server to stop listening
unexpectedly is if an exception is thrown while the aborted connection is
being cleaned up.

> And if that's true, how should I protect TIdTCPServer::OnExecute()
> from stopping functioning?

You don't, because that is not the correct place to do that.  OnExecute is
already protected from uncaught exceptions.  If an exception escapes the
OnExecute event handler, the connection with the client is closed and the
server contnues listening for new connections.

> How to know the number of active connections/threads?

Use the Threads property, ie:

    TList *List = IdTCPServer1->Threads->LockList;
    // look at List->Count
    IdTCPServer1->Threads->UnlockList;

> To be more precise, I want to limit the heavy operations
> in TIdTCPServer::OnExecute() so I want to do something
> like this /PSEUDO/:

You are taking the wrong approach.  To limit the number of allowed
connections, simply set the TIdTCPServer's MaxConnections property before
activating the server.  Unlike TServerSocket, TIdTCPServer keeps track of
the number of connections and automatically closes them when too many are
connected.  TIdTCPServer also has a MaxConnectionReply property that can be
used to send a reply back to the client telling it that there are too many
connections.

Also, your code suggests that you are not aware that the OnExecute event is
not called once per connection.  It is called over and over.  Unlike
TServerSocket, where you have to implement your own looping when you write a
thread class, TIdTCPServer loops the OnExecute event automatically.  So, if
you had TServerSocket code like the following:

    void __fastcall TMyThread::ClientExecute()
    {
        //...
        while( !Terminated && ClientSocket->Connected )
        {
            // read a block...
            // write a block...
        }
        //...
    }

Your TIdTCPServer code would look like this:

    void __fastcall TForm1::IdTCPServer1Execute(AThread: TIdPeerThread)
    {
        // read a block...
        // write a block...
    }

>       // Sending the request length to server ...
>       IdTCPClient->WriteInteger( REQUEST.Length() );

Why are you sending the string's length if you are not going to send the
string directly?

>       TStringList *SL = new TStringList;
>       SL->Add( REQUEST );
>       IdTCPClient->WriteStrings( SL );
>       delete SL;

Why are you using a TStringList to send a single string?  That is very
wasteful of memory.  Not only that, but WriteStrings() will write more data
than you have specified with WriteInteger().  Specifically, WriteStrings()
uses WriteLn(), so your request will have an extra CRLF at the end of it.
Based on the server code you have shown, you need to use Write() instead of
WriteStrings():

    // Sending the request to server ...
    IdTCPClient->WriteInteger( REQUEST.Length() );
    IdTCPClient->Write( REQUEST );

>    // Receive request ...
>    AnsiString REQUEST = AThread->Connection->ReadString( BytesToRead );

That will not work because WriteStrings() sent an extra CRLF for each line
that you are not reading.  That CRLF will remain in the socket until the
next read, which will then corrupt the reading of the integer value for the
next request.

>    Memo->Lines->Add( REQUEST + " (received)" );
<snip>
>    Memo->Lines->Add( RESPONSE + " (prepared)" );
<snip>
>    Memo->Lines->Add( RESPONSE + " (sent)" );

That is not thread-safe on the server side.

>    // Send to respondse to client ...
>    TStringList *SL = new TStringList;
>    SL->Add( RESPONSE );
>    AThread->Connection->WriteStrings( SL );
>    delete SL;

Again, that is very wasteful of memory, and will corrupt the communication
because of the extra CRLF that you are not taking into account.

Now, with all of that said, I suggest a different approach - make use of
Indy's CommandHandler system.  For example:

    --- Client ---

    void __fastcall TIndyClientForm::AskButtonClick(TObject *Sender)
    {
        AskButton->Enabled = false;

        try
        {
            IdTCPClient->Connect(5000);
            try
            {
                IdTCPClient->SendCmd("TIME", 200);
                Memo->Lines->Add(IdTCPClient->LastCmdResult->Text);
            }
            __finally
            {
                IdTCPClient->Disconnect();
            }
        }
        catch(const Exception &e)
        {
            Memo->Lines->Add("ERROR: " + e.Message);
        }

        AskButton->Enabled = true;
    }


    --- Server ---

    __fastcall TIndyServerForm::TIndyServerForm(TComponent *Owner)
        : TForm(Owner)
    {
        // you can set this stuff up at design-time instead...
        TIdCommandHandler *Command = IdTCPServer->CommandHandlers->Add();
        Command->Command = "TIME";
        Command->OnCommand = CommandTIME;
        Command->ReplyNormal->NumericCode = 200;
        Command->ReplyExceptionCode = 500;
        Command->ParseParams = false;

        IdTCPServer->Active = true;
    }

    void __fastcall TIndyServerForm::CommandTIME(TIdCommand *ASender)
    {
        ASender->Reply->SetReply(200, Now().FormatString("hh:mm AM/PM"));
    }


Gambit
Technical Details
Message-ID: <43455dd4$1@newsgroups.borland.com>
Path: arc1-ca.pugleaf.net!g2news1.google.com!news3.google.com!news.glorb.com!mpls-transit-01.news.qwest.net!38.119.100.82.MISMATCH!news-out.newsgroups.com!propagator2-sterling.newsfeeds.com!in.nntp.be!caladan.landsraad.net!newsgroups.borland.com!not-for-mail
References: <434503e6$1@newsgroups.borland.com>

Show Full Headers
Complete Article Headers
X-Google-Thread: 11508e,cf1d5664f47b34d3
X-Google-Attributes: gid11508e,public
X-Google-Language: ENGLISH,ASCII-7-bit
From: "Remy Lebeau \(TeamB\)" <no.spam@no.spam.com>
Newsgroups: borland.public.cppbuilder.internet.socket
References: <434503e6$1@newsgroups.borland.com>
Subject: Re: Indy TCP C/S: Timeout , Concurrent access, Number of threads
Date: Thu, 6 Oct 2005 10:21:02 -0700
Lines: 224
X-Priority: 3
X-MSMail-Priority: Normal
X-Newsreader: Microsoft Outlook Express 6.00.2800.1409
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2800.1409
NNTP-Posting-Host: 68.69.17.41
Message-ID: <43455dd4$1@newsgroups.borland.com>
X-Trace: newsgroups.borland.com 1128619476 68.69.17.41 (6 Oct 2005 10:24:36 -0700)
Path: arc1-ca.pugleaf.net!g2news1.google.com!news3.google.com!news.glorb.com!mpls-transit-01.news.qwest.net!38.119.100.82.MISMATCH!news-out.newsgroups.com!propagator2-sterling.newsfeeds.com!in.nntp.be!caladan.landsraad.net!newsgroups.borland.com!not-for-mail