🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

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

Re: TClientSocket handling a bad connection

#382
From: Remy Lebeau (TeamB)
Date: Tue, 01 Apr 2008 18:31
208 lines
8221 bytes
"Lesley Anne" <mspfila (dash) brl (at) yahoo (dot) com> wrote in message 
news:47f26db7@newsgroups.borland.com...

> But it doesn't call the event handler if the socket is invalid.
> Would it be invalid in the case of a WSAENOTSOCK error?

Yes.  The TClientSocket's FSocket data member has not been reset to 
INVALID_SOCKET yet.

> This goes back to the question just above.  Would we have
> an INVALID_SOCKET when some network glitch causes a
> connection to get WSAENOTSOCK errors (after becoming
> Active and getting an OnConnect event)?

Not without a prior seDisconnect notification being issued first, no.

> In that case, the seDisconnect notification wouldn't be processed,
> since if the Socket is INVALID_SOCKET, then the Disconnect
> function exits early, before the seDisconnect notification.

The only time the FSocket member is ever reset back to INVALID_SOCKET is 
inside of TCustomWinSocket.Disconnect(), after issuing a seDisconnect 
notification.  Disconnect() is called in several places, such as after an 
eeSend error when sending data.  If you are getting the OnConnect event 
fired, then the FSocket member should be valid, and Disconnect() will fire 
the OnDisconnect event the first time it is called, even after a send error 
occurs.

> If I have an invalid socket, how do I clean it up and get my
> TClientSocket component back to its normal disconnected
> non-Active state?

You may have to destroy and re-instantiate the TClientSocket object anew.

> But in non-blocking mode, I don't expect it to have already
> been sent.

It is not sent immediately.  But the socket does tell you how much data it 
was actually able to accept without blocking.  But if it does enter a 
blocking state, you must handle that properly, or you won't be able to send 
your remaining data correctly at all, and your communication will become 
corrupted since you are discarding your data too early.

> Maybe I'm misunderstanding that explanation, but it sounds
> like it's effectively making the "non-blocking" mode blocking.

No.  It is still a non-blocking event-driven model.  You are simply holding 
on to your data longer when the socket tells you to, and then re-sending it 
when the socket reports it is ready for it.

> In fact it's a particularly convoluted blocking in which one
> event has to wait for several others before it can continue.

That is the pitfall to using non-blocking sockets in general.  Every 
operation you perform may put the socket into a busy state.  If you are 
sending data when it happens, you must wait for the socket to report it is 
ready again before you can send data again.  That is what the OnWrite event 
is for - the socket reporting that it has become writable.  Most people who 
use non-blocking sockets ignore that step, mainly because they simply don't 
know any better.  It it not common to hit on, but it still possible and 
should be coded for.

When I use non-blocking sockets (which is not common nowadays, I usually use 
blocking instead), I use an intermediate buffer when needed.  When writing 
any data to a connection, I first check if the buffer is empty.  If it is 
not, I add the data into the buffer and move on.  Otherwise, I pass the data 
to the socket instead, and if it reports -1 without an OnError event 
(TCustomWinSocket does not fire the OnError event for WSAEWOULDBLOCK) then I 
put the data into the buffer and move on.  Either way, whenever the OnWrite 
event is fired, I pass whatever data is in the buffer to the socket, 
removing only as much as the socket said it could accept (if you have a lot 
of data buffered, another WSAEWOULDBLOCK could occur while you are still 
inside the OnWrite event).

Here is some code to demonstrate it better:

    TMemoryStream *Buffer;

    __fastcall TForm1::TForm1(TComponent *Owner)
        : TForm(Owner)
    {
        Buffer = new TMemoryStream;
    }

    __fastcall TForm1::~TForm1()
    {
        delete Buffer;
    }

    int __fastcall TForm1::SendDataToSocket(void *Data, int Size)
    {
        LPBYTE DataPtr = (LPBYTE) Data;
        int TotalNumSent = 0;

        try
        {
            while( Size > 0 )
            {
                int NumSent = ClientSocket->Socket->SendBuf(DataPtr, Size);
                if( NumSent < 1 )
                {
                    // WSAEWOULDBLOCK occured, or the server disconnected.
                    // Otherwise, an exception would have been raised, 
unless the
                    // OnError handler set the ErrorCode to 0, which I do 
not
                    // recomment in this situation...
                    break;
                }

                DataPtr += NumSent;
                Size -= NumSent;
                TotalNumSent += NumSent;
            }
        }
        catch(const Exception &)
        {
        }

        return TotalNumSent;
    }

    bool __fastcall TForm1::SendData(void *Data, int Size)
    {
        LPBYTE DataPtr = (LPBYTE) Data;

        if( Buffer->Size == 0 )
        {
            int NumSent = SendDataToSocket(DataPtr, Size);
            if( NumSent > 0 )
            {
                DataPtr += NumSent;
                Size += NumSent;
            }
        }

        if( Size > 0 )
        {
            Buffer->Seek(0, soFromEnd);
            Buffer->Write(DataPtr, Size);
        }

        return true;
    }

    bool __fastcall TForm1::SendText(const AnsiString &Str)
    {
        return SendData(Str.c_str(), Str.Length());
    }

    void __fastcall TForm1::ClientSocket1Connect(TObject *Sender, 
TCustomWinSocket *Socket)
    {
        Buffer->Clear();
    }

    void __fastcall TForm1::ClientSocket1Disconnect(TObject *Sender, 
TCustomWinSocket *Socket)
    {
        Buffer->Clear();
    }

    void __fastcall TForm1::ClientSocketWrite(TObject *Sender, 
TCustomWinSocket *Socket)
    {
        if( Buffer->Size == 0 )
            return;

        LPBYTE DataPtr = (LPBYTE) Buffer->Memory;
        Buffer->Position = 0;

        do
        {
            int NumSent = SendDataToSocket(DataPtr, Buffer->Size - 
Buffer->Position);
            if( NumSent < 1 )
                break;

            Buffer->Seek(NumSent, soFromCurrent);
            DataPtr += NumSent;
        }
        while( Buffer->Position < Buffer->Size );

        if( Buffer->Position > 0 )
        {
            int NewSize = (Buffer->Size - Buffer->Position);

            if( NewSize > 0 )
                memcpy(Buffer->Memory, DataPtr, NewSize);

            Buffer->Size = NewSize;
        }
    }

> Would Connected give me a more reliable indication of when it's ready?

Why not simply disable your timer when calling Open(), and then reenable it 
when OnConnect is fired?

> (I am seeing the OnError event handler /after/ the OnConnect event
> handler, but maybe it's possible that the SendText call which led to
> the error happened sooner, and before the OnConnect.)

Possibly.


Gambit 
Technical Details
Message-ID: <47f27f91$1@newsgroups.borland.com>
Path: arc1-ca.pugleaf.net!g2news1.google.com!news1.google.com!border1.nntp.dca.giganews.com!border2.nntp.dca.giganews.com!nntp.giganews.com!newspeer1.nac.net!newsfeeds.ihug.co.nz!ihug.co.nz!newsgroups.borland.com!not-for-mail
References: <47f16766$1@newsgroups.borland.com> <47f16f19$1@newsgroups.borland.com> <47f26db7@newsgroups.borland.com>

Show Full Headers
Complete Article Headers
X-Google-Thread: 11508e,74b475fd14f26423
X-Google-Attributes: gid11508e,domainid0,public,usenet
X-Google-Language: ENGLISH,ASCII-7-bit
From: "Remy Lebeau \(TeamB\)" <no.spam@no.spam.com>
Newsgroups: borland.public.cppbuilder.internet.socket
References: <47f16766$1@newsgroups.borland.com> <47f16f19$1@newsgroups.borland.com> <47f26db7@newsgroups.borland.com>
Subject: Re: TClientSocket handling a bad connection
Date: Tue, 1 Apr 2008 11:31:42 -0700
Lines: 210
Organization: Lebeau Software
X-Priority: 3
X-MSMail-Priority: Normal
X-Newsreader: Microsoft Outlook Express 6.00.2900.3138
X-RFC2646: Format=Flowed; Response
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.3198
NNTP-Posting-Host: 207.178.157.90
Message-ID: <47f27f91$1@newsgroups.borland.com>
X-Trace: newsgroups.borland.com 1207074705 207.178.157.90 (1 Apr 2008 11:31:45 -0700)
Path: arc1-ca.pugleaf.net!g2news1.google.com!news1.google.com!border1.nntp.dca.giganews.com!border2.nntp.dca.giganews.com!nntp.giganews.com!newspeer1.nac.net!newsfeeds.ihug.co.nz!ihug.co.nz!newsgroups.borland.com!not-for-mail
X-Original-Bytes: 8071