Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

TClientSocket handling a bad connection

1,642 views
Skip to first unread message

Lesley Anne dash at dot

unread,
Mar 31, 2008, 6:34:12 PM3/31/08
to
(BCB6)
I have two apps, one using TClientSocket, one using TServerSocket, both in
non-blocking mode. Across a LAN, it works. Across a WAN, it sometimes
works, but we get the following failure about once each day:

1) Client tries to connect to the server.
Both Client and Server get OnConnect event.

2) Client calls SendText to send a message to the server.
Server sees nothing.
Client gets OnError event with: ErrorEvent == eeSend, ErrorCode == 10038
(WSAENOTSOCK)
Within the OnError event handler, Client calls Socket->Close()
No OnDisconnect events occur, and Client remains Active.

3) Every second, the Client calls SendText a few more times.
Nothing reaches the server.

4) After 5 minutes, the server times out, deciding that this connection
(which it hasn't received anything on) is bad, so closes it.
Server gets OnDisconnect event.
Client sees nothing.

5) Client (which is still Active and has never gotten an OnDisconnect event)
continues trying to send on this connection which had never worked properly
and which now no longer even exists at the server's end.


I think part of the fix is likely to be that I should redesign the client
app to be multi-threaded so that I can use blocking sockets. That way at
least the client will be able to recognize that the SendText didn't work in
time for me to avoid removing the message from the FIFO queue I use for
spooling messages that need to be sent. That's more of a symptomatic fix,
though. It doesn't really address the issue of why the error is occurring
in the first place.

Both sides of the connection did see the OnConnect event. Shouldn't that
indicate that the connection was established successfully? So why does the
client get the WSAENOTSOCK error when it tries to send? And why does it
never get the OnDisconnect event? If the connection is down, how can I
force the client to recognize that it's down, so that I can stop sending and
try to reconnect until I get a working connection? (Calling Socket->Close
from the OnError event was an attempt to do this. I was hoping that would
bring down the client side of the non-functioning "connection", so I could
re-establish it.)

I'm not quite sure which is the most relevant part of the code, but sending
is where the error occurs, so here's where I get to the SendText call from
the client. I could include other parts of the source, but they don't do
much. The OnConnect and OnDisconnect events just update status indicators
on the screen and write to a logfile to let me know they happened. The
OnError event does the same, plus calls Socket->Close() as I already
mentioned.

(BESpoolHead is a global pointer to the first node in a linked list. It's a
FIFO queue of messages that need to be sent.)

// -----------------------------------------------------------------------
void __fastcall TCnamForm::PerSecondTimer(TObject *Sender)
{
TStringSpoolNode *TempNode;

for (int i=0; BESpoolHead && (i<500); i++) {
if (SendToBE(BESpoolHead->Data)) {
TempNode = BESpoolHead->Link;
delete BESpoolHead;
BESpoolHead = TempNode;
}
else break;
}
}
// -----------------------------------------------------------------------
bool TCnamForm::SendToBE(AnsiString Message)
{
bool Success = false;

if (BEClientSocket->Active) {
try {
BEClientSocket->Socket->SendText(Message + "\n");
Success = true;
}
catch (Exception &E) {
LogMessage("ERROR Sending to Back End. Exception: " + E.Message);
}
}

return Success;
}
// -----------------------------------------------------------------------

One of my problems here is that, since I'm in non-blocking mode, that
try-catch around SendText isn't really doing anything very useful. That's
why I think I need to redesign this to use blocking sockets, so I can see
whether the send really was successful before deciding whether I can take
the message off of the spool.

Oh, here's another part of the code that looks relevant. I have a timer
that runs continuously and re-establishes the connection if it's down and
needs to be up. (The BEIndicator->Brush->Color it references gets set to
clLime during OnConnect and set to clRed in either the OnError or
OnDisconnect event. NeedBEConnect is a global bool which is true throughout
the time that all this is happening.)

// -----------------------------------------------------------------------
void __fastcall TCnamForm::ReconnectTimerTimer(TObject *Sender)
{
bool BackEndIsDown = ((!BEClientSocket->Active) ||
(BEIndicator->Brush->Color != clLime));

if (NeedBEConnect && BackEndIsDown) {
try {
BEClientSocket->Close();
BEClientSocket->Open();
}
catch (Exception &E) {
LogMessage("ERROR Opening Back End Socket. Exception: " +
E.Message);
}
}
}
// -----------------------------------------------------------------------


Remy Lebeau (TeamB)

unread,
Mar 31, 2008, 7:09:02 PM3/31/08
to

"Lesley Anne" <mspfila (dash) brl (at) yahoo (dot) com> wrote in message
news:47f16766$1...@newsgroups.borland.com...

> 2) Client calls SendText to send a message to the server.

Where exactly are you doing that? For a non-blocking socket, where you
execute reading/writing operations makes a difference.

> Server sees nothing.

Then the data was not transmitted at all.

> Client gets OnError event with: ErrorEvent == eeSend,
> ErrorCode == 10038 (WSAENOTSOCK)

Is the connection passing through a router or firewall?

> Within the OnError event handler, Client calls Socket->Close()
> No OnDisconnect events occur

It has to be. TCustomWinSocket.Close() fires the OnDisconnect event handler
directly before then destroying the socket handle.

> and Client remains Active.

The only way it can be active after Close() is called is if the seDisconnect
notification is never being processed at all.

> 3) Every second, the Client calls SendText a few more times.
> Nothing reaches the server.

Then the client is not actually connected to the server.

> 4) After 5 minutes, the server times out, deciding that this connection
> (which it hasn't received anything on) is bad, so closes it.
> Server gets OnDisconnect event.
> Client sees nothing.

Then the client is not actually connected to the server.

> 5) Client (which is still Active and has never gotten an OnDisconnect
> event) continues trying to send on this connection which had never
> worked properly and which now no longer even exists at the server's end.

This suggests there are bugs in your code that are preventing TClientSocket
from operating properly in the first place.

> I think part of the fix is likely to be that I should redesign the client
> app to be multi-threaded so that I can use blocking sockets.

That is not necessary. TClientSocket in non-blocking mode works just fine
when used properly. It has been a very solid and stable component for a
long time.

> That way at least the client will be able to recognize that the SendText
> didn't work

You need to use a TWinSocketStream when reading/writing a blocking socket.
You can't use SendText() or SendBuf() anymore.

> in time for me to avoid removing the message from the FIFO queue
> I use for spooling messages that need to be sent.

That in itself tells me that you are probably not managing the queue
properly. For one thing, you must look at the return value of SendText() or
SendBuf() in order to know if the entire string was sent at all. If it is
not, and the result is -1 without an OnError event being triggered, then you
must wait for the OnWrite event to fire and then re-sent the same data again
before then sending any subsequent new data.

> Both sides of the connection did see the OnConnect event.
> Shouldn't that indicate that the connection was established
> successfully?

Perhaps, but that does not account for any mismanagement you may be doing
after connecting. It also does not take into account if a firewall/router
is forcibly killing the socket endpoint on the client side after connecting.

> So why does the client get the WSAENOTSOCK error when it tries to send?

You are likely trying to send the data too early. Between when Open() is
called and the OnConnect event is fired, you cannot sent data to the socket.
It is not ready yet. You must wait for the OnConnect event first. You also
must wait for the OnWrite event if SendText() or SendBuf() ever return -1
without firing the OnError event.

> And why does it never get the OnDisconnect event?

Are you assigning a handler to the client's OnSocketEvent event, by chance?

> If the connection is down, how can I force the client to recognize

> that it's down so that I can stop sending and try to reconnect until


> I get a working connection?

Find and fix the bug in your your client code that is preventing the
TClientSocket from operating correctly.

> Calling Socket->Close from the OnError event was an attempt to do this.
> I was hoping that would bring down the client side of the non-functioning
> "connection", so I could re-establish it.

Under normal situations, it does.

> void __fastcall TCnamForm::PerSecondTimer(TObject *Sender)

> BEClientSocket->Socket->SendText(Message + "\n");

You are completely ignoring the return value of SendText(). You must check
it to know how much data was actually sent, adjusting the FIFO item to
account for partial transmissions and blocking transmissions.

> One of my problems here is that, since I'm in non-blocking mode,
> that try-catch around SendText isn't really doing anything very useful.

An exception would only be raised if the OnError event handler does not set
the ErrorCode value to 0.

> That's why I think I need to redesign this to use blocking sockets, so
> I can see whether the send really was successful before deciding
> whether I can take the message off of the spool.

You can do that with a non-blocking socket as well.


Gambit


Lesley Anne dash at dot

unread,
Apr 1, 2008, 1:13:19 PM4/1/08
to

"Remy Lebeau (TeamB)" wrote in message
news:47f16f19$1...@newsgroups.borland.com...
>
> "Lesley Anne" wrote in message news:47f16766$1...@newsgroups.borland.com...

>
> > Client gets OnError event with: ErrorEvent == eeSend,
> > ErrorCode == 10038 (WSAENOTSOCK)
>
> Is the connection passing through a router or firewall?

Yes. In fact, we have two instances of the client application. The local
one which only passes through a switch doesn't have this problem. The other
one which passes through a couple of routers with firewalls and a
long-distance circuit is the one where the problem occurs. This certainly
sounds like the initial source of the problem is something getting dropped
by the network, and some of my co-workers who are more familiar than I am
with the routers and firewalls are looking into that. In the meantime, I'm
trying to make the software more robust and tolerant of network glitches.


> > Within the OnError event handler, Client calls Socket->Close()
> > No OnDisconnect events occur
>
> It has to be. TCustomWinSocket.Close() fires the OnDisconnect event
> handler directly before then destroying the socket handle.

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


> > and Client remains Active.
>
> The only way it can be active after Close() is called is if the
> seDisconnect notification is never being processed at all.

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)? 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. Given the symptoms I'm seeing, it looks like
that may be what's happening. 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?


> For one thing, you must look at the return value of SendText() or
> SendBuf() in order to know if the entire string was sent at all.

But in non-blocking mode, I don't expect it to have already been sent. I
don't have time to let my main thread wait while the send actually occurs so
that it can see whether it was successful or not. (That's what blocking
mode would be for, but it requires a separate thread if I don't want my main
VCL thread to be waiting for it.)


> If it is not, and the result is -1 without an OnError event being
> triggered, then you must wait for the OnWrite event to fire and then
> re-sent the same data again before then sending any subsequent new data.

Maybe I'm misunderstanding that explanation, but it sounds like it's
effectively making the "non-blocking" mode blocking. In fact it's a
particularly convoluted blocking in which one event has to wait for several
others before it can continue. There's the initial event that has something
it wants to send, but it has to wait for separately triggered OnError and
OnWrite events to occur (or not occur) before it can continue, and it has to
somehow communicate with those other events, which it is neither calling nor
being called by. I don't really understand how all of these get
coordinated.


> You are likely trying to send the data too early. Between when Open() is

> called and the OnConnect event is fired, you cannot send data to the

> socket. It is not ready yet. You must wait for the OnConnect event first.

OK, that makes sense. I saw where you cautioned someone else to check
Connected rather than Active. I can try changing that. Would Connected
give me a more reliable indication of when it's ready? (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.)


> > And why does it never get the OnDisconnect event?
>
> Are you assigning a handler to the client's OnSocketEvent event, by
> chance?

No. Just the OnConnect, OnDisconnect, and OnError events (all from the
TCustomSocket layer of the inheritance if I understand it correctly).


Remy Lebeau (TeamB)

unread,
Apr 1, 2008, 2:31:42 PM4/1/08
to

"Lesley Anne" <mspfila (dash) brl (at) yahoo (dot) com> wrote in message
news:47f2...@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


Mike Ruskai

unread,
Apr 1, 2008, 3:24:24 PM4/1/08
to
On or about Mon, 31 Mar 2008 18:34:12 -0400 did "Lesley Anne" <mspfila (dash)
brl (at) yahoo (dot) com> dribble thusly:

>(BCB6)
>I have two apps, one using TClientSocket, one using TServerSocket, both in
>non-blocking mode. Across a LAN, it works. Across a WAN, it sometimes
>works, but we get the following failure about once each day:

[snip]

Do yourself a big favor and go download a copy of Ethereal. Capture your
network traffice, so you can see exactly what's happening with the socket.

Look for the outbound SYN and inbound ACK to establish that a connection was
made, and then outbound data and inbound ACK for the data transmission.

That will show pretty definitively if you should bother looking for a network
fault.

Lesley Anne dash at dot

unread,
Apr 1, 2008, 8:12:13 PM4/1/08
to
"Remy Lebeau (TeamB)" wrote in message
news:47f27f91$1...@newsgroups.borland.com...
>
> "Lesley Anne" wrote in message news:47f2...@newsgroups.borland.com...

>
> 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.

Disconnect should fire the OnDisconnect event, but somehow it isn't
happening, at least not once this error occurs.

Here are my event handlers. I should see every time the OnConnect or
OnDisconnect events fire, and the first time the OnError event fires after
an OnConnect or OnDisconnect. (I do suppress display of subsequent error
messages in order to avoid having the app try to write hundreds of identical
error messages every second when a problem is occurring. Because of this, I
only know that the first error I get is WSAENOTSOCK. There may be others
after this that I'm not seeing. I might change this to also display any
error if its ErrorCode is different than the last one received.)

// ----------------------------------------------------------------------
void __fastcall TForm1::BEClientSocketConnect(TObject *Sender,
TCustomWinSocket *Socket)
{
LogMessage("Connection Established to Back End. " +
BEClientSocket->Address);
BEIndicator->Brush->Color = clLime;
BEError1Displayed = false;
BEError2Displayed = false;
UpdateAlarms();
}
// ----------------------------------------------------------------------
void __fastcall TForm1::BEClientSocketDisconnect(TObject *Sender,
TCustomWinSocket *Socket)
{
LogMessage("Connection Dropped to Back End. " + BEClientSocket->Address);
BEIndicator->Brush->Color = clRed;
BEError1Displayed = false;
BEError2Displayed = false;
UpdateAlarms();
}
// ----------------------------------------------------------------------
void __fastcall TForm1::BEClientSocketError(TObject *Sender,
TCustomWinSocket *Socket, TErrorEvent ErrorEvent, int &ErrorCode)
{
if (!BEError1Displayed) {
String ErrorMessage = "ERROR: Back End Socket Error, ErrorEvent=";
switch (ErrorEvent) {
case eeGeneral: ErrorMessage += "General"; break;
case eeSend: ErrorMessage += "Send"; break;
case eeReceive: ErrorMessage += "Receive"; break;
case eeConnect: ErrorMessage += "Connect"; break;
case eeDisconnect: ErrorMessage += "Disconnect"; break;
case eeAccept: ErrorMessage += "Accept"; break;
default: ErrorMessage += "(unrecognized)";
}
ErrorMessage += " ErrorCode=" + String(ErrorCode);
LogMessage(ErrorMessage);

BEError1Displayed = true;
}

BEIndicator->Brush->Color = clRed;
ErrorCode = 0;
Socket->Close();
}
// ----------------------------------------------------------------------

The event handlers all do fire until this error occurs. The error event
calls Socket->Close(), which should then give an OnDisconnect, but that
never happens. Neither does the Active property get set to false, as should
also happen in the call to Event(Self, seDisconnect). Somehow Close appears
not to be making it all the way through the Disconnect function. If it's
not due to the exit after that INVALID_SOCKET check, then the only other
things I can see that could be happening is that either the try block in
Disconnect somehow isn't making it past the first few lines, and is jumping
out to its finally block before reaching the Event call, or else it's
picking up TCustomWinSocket.Event instead of TCustomSocket.Event which is
where OnDisconnect is. (Actually, I don't really see how it would pick up
the TCustomSocket version of Event, but if it wasn't, then the events
wouldn't have been working in the first place.)


{ ***************************************** }
{ from ScktComp.pas }
{ ***************************************** }

procedure TCustomWinSocket.Close;
begin
Disconnect(FSocket);
end;

procedure TCustomWinSocket.Disconnect(Socket: TSocket);
begin
Lock;
try
if FLookupHandle <> 0 then
CheckSocketResult(WSACancelASyncRequest(FLookupHandle),
'WSACancelASyncRequest');
FLookupHandle := 0;
if (Socket = INVALID_SOCKET) or (Socket <> FSocket) then exit;
Event(Self, seDisconnect);
CheckSocketResult(closesocket(FSocket), 'closesocket');
FSocket := INVALID_SOCKET;
FAddr.sin_family := PF_INET;
FAddr.sin_addr.s_addr := INADDR_ANY;
FAddr.sin_port := 0;
FConnected := False;
FreeAndNil(FSendStream);
finally
Unlock;
end;
end;

procedure TCustomWinSocket.Event(Socket: TCustomWinSocket; SocketEvent:
TSocketEvent);
begin
if Assigned(FOnSocketEvent) then FOnSocketEvent(Self, Socket,
SocketEvent);
end;

procedure TCustomSocket.Event(Socket: TCustomWinSocket; SocketEvent:
TSocketEvent);
begin
case SocketEvent of
{ snip other cases }
seDisconnect:
begin
FActive := False;
if Assigned(FOnDisconnect) then FOnDisconnect(Self, Socket);
end;
end;
end;


{ ***************************************** }


Also, in addition to that call to Socket->Close in the OnError handler
itself, I also have the ReconnectTimer (shown at the end of my first post in
this thread) which is periodically attempting
BEClientSocket->Close();
BEClientSocket->Open();
for as long as the socket remains in this error state (as shown by that
indicator being set to clRed). None of these Close or Open attempts work
either. Once the socket gets this error, neither OnConnect nor OnDisconnect
events occur again. Active remains true, but there is no communication with
the server app (which times out after a while, closing its side of the
connection).

Well, tonight a couple of my co-workers are going to set up a trap in the
nearest router to see what it sees going across the connection. Maybe that
will shed some light on what's happening. The only other thing I know right
now is that the problem occurs around midnight when the server closes the
connection for a few moments to do some other processing. When it re-opens
and the clients reconnect (or try to) that's when the remote client gets
this error.


> 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.

I seem to be hitting on it. Even apart from this weird error stage, even my
good connections are occasionally dropping some data where the server
doesn't see as much as the client thinks it's sending, (particularly when
traffic is heavy). It sounds like missing this OnWrite processing is the
culprit for that problem.


> 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:

Thanks. I think I see how this is supposed to work now. I'll work with
that a little and see if I can adapt it to my app. (I'm also trying to keep
track of how many messages have been sent, so I can compare it to what the
server is receiving at its end. And any messages that haven't been sent
during one connection need to be saved and sent next time I reconnect. I
should be able to adapt that Buffer for those concerns, though.) Or I may
still end up deciding to switch to a blocking socket anyway, although that
brings in its own complexities with threading. (Incidentally, which is the
right newsgroup on which to ask questions about threads and critical
sections?)


> // Otherwise, an exception would have been raised, unless the
> // OnError handler set the ErrorCode to 0, which I do not
> // recomment in this situation...

Currently I do have the event handler setting ErrorCode to 0. This is
because, since the socket is non-blocking and continues to operate after
having returned from the function call, I would no longer be in the
try-catch block at that point. I'm worried about errors that occur on the
socket that occur after having returned from the SendText or SendBuf call
with WSAEWOULDBLOCK. The socket itself is still sending, so presumably
could still hit a sending error. I want to make sure that my OnError
handler finishes handling any error that I don't have a catch for.


Remy Lebeau (TeamB)

unread,
Apr 1, 2008, 9:15:55 PM4/1/08
to

"Lesley Anne" <mspfila (dash) brl (at) yahoo (dot) com> wrote in message
news:47f2...@newsgroups.borland.com...

> ErrorCode = 0;
> Socket->Close();

What happens if you comment those out and just let OnError exit without
changing anything?

> The event handlers all do fire until this error occurs. The error
> event calls Socket->Close(), which should then give an OnDisconnect

It should. Have you tried stepping through the TClientSocket source code in
the debugger yet?

> but that never happens. Neither does the Active property get set to false

Because seDisconnect is not being processed.

> Somehow Close appears not to be making it all the way
> through the Disconnect function. If it's not due to the exit
> after that INVALID_SOCKET check

Inside the OnConnect event handler, what is the value of the
TCustomWinSocket.SocketHandle property? It should not be -1. If it is not,
then there is no way the INVALID_SOCKET check could be failing, since the
SocketHandle cannot be reset to INVALID_SOCKET without going through
seDisconnect first.

> then the only other things I can see that could be happening is
> that either the try block in Disconnect somehow isn't making it
> past the first few lines, and is jumping out to its finally block
> before reaching the Event call

Once INVALID_SOCKET is ruled out. The only way that can be happening is if
an exception is being raised. The debugger can tell you that.

> Also, in addition to that call to Socket->Close in the OnError handler
> itself, I also have the ReconnectTimer (shown at the end of my first post
> in this thread) which is periodically attempting
> BEClientSocket->Close();
> BEClientSocket->Open();
> for as long as the socket remains in this error state (as shown by that
> indicator being set to clRed). None of these Close or Open attempts
> work either. Once the socket gets this error, neither OnConnect nor
> OnDisconnect events occur again. Active remains true, but there is
> no communication with the server app (which times out after a while,
> closing its side of the connection).

Have you tried disabling that timer altogether until we figure out why the
TClientSocket is in such a bad state? Don't even enable it at all. Take
out everything you can from your code so you have the most bare-bones
reproducable scenerio to get into the error state.

> The only other thing I know right now is that the problem occurs around
> midnight when the server closes the connection for a few moments to do
> some other processing. When it re-opens and the clients reconnect (or
> try to) that's when the remote client gets this error.

You did not say that before. That is an important detail to know. The
server may be killing the socket endpoints of existing clients incorrectly,
invaliding TClientSocket's endpoint before you can send any data to it.
That might explain the WSAENOTSOCK error, but that still does not explain
how TClientSocket could be tracking it so badly.


Gambit


0 new messages