Thread View: borland.public.cppbuilder.internet.socket
6 messages
6 total messages
Started by Mark Jacobs
Sat, 31 Dec 2005 10:42
Non-blocking socket - how to detect when server has finished writing to client
Author: Mark Jacobs
Date: Sat, 31 Dec 2005 10:42
Date: Sat, 31 Dec 2005 10:42
27 lines
2223 bytes
2223 bytes
I have a TClientSocket which is acting like a POP3 mail reader. I get all the headers and put them into a sortable
StringGrid, so I can turf out all the spam before I download the message bodies.
I have one remaining problem which is to do with the delay I am having to put in the socket read routine in order
to get it to work properly. It is still not perfect, but here is the code :-
// Globals char *bf, int wh=0; and bool dun=false; to begin with.
//---------------------------------------------------------------------------
void __fastcall TForm1::ClientSocket1Read(TObject *Sender,TCustomWinSocket *Socket)
{
int ii,jj=Socket->ReceiveLength();
while (jj>0)
{
ii=Socket->ReceiveBuf(&bf[wh],jj);
wh+=ii; bf[wh]='\0'; Sleep(200); jj=Socket->ReceiveLength();
}
dun=true;
}
How do you detect when the server has finished sending data to the client in a non-blocking environment?
Without the Sleep command, ReceiveLength can sometimes report 0 bytes to read, despite there being more
waiting at the server end to be sent. The delay of a fifth of a second works most of the while on a broadband
connection (not always), but it really slows the app down. I have also tried if (Socket->LookupState==lsIdle)
dun=true; and that was no better. Any ideas?
Mark Jacobs
Re: Non-blocking socket - how to detect when server has finished writing to client
Author: Hans Galema
Date: Sat, 31 Dec 2005 15:01
Date: Sat, 31 Dec 2005 15:01
36 lines
2255 bytes
2255 bytes
Mark Jacobs wrote:
> void __fastcall TForm1::ClientSocket1Read(TObject *Sender,TCustomWinSocket *Socket)
> {
> int ii,jj=Socket->ReceiveLength();
> while (jj>0)
> {
You do not need a loop here as for sure ReceiveBuf will get
all the bytes indicated by ReceiveLength.
> ii=Socket->ReceiveBuf(&bf[wh],jj);
> wh+=ii; bf[wh]='\0'; Sleep(200); jj=Socket->ReceiveLength();
> }
> dun=true;
> }
For the rest the use of this loop with a Sleep and then checking
ReceiveLEngth() again is against the idea of the OnRead event.
Just exit the the OnRead handler and wait for the next event. Indeed
then you have to buffer the incoming data. Using a TMemoryStream makes
this simple.
> How do you detect when the server has finished sending data to
> the client in a non-blocking environment?
That you have to determine on the server. The server migth
well have send all long time ago. Winsocks buffers incoming
data and TClientSocket gives it in small amounts in the
OnRead event.
If you want to determine on the receiving side if all data belonging
to a message has come in then you need a protocol that
for instance sends the size of the message in an 4 byte integer first.
Hans.
Re: Non-blocking socket - how to detect when server has finished writing to client
Author: Vladimir Stefanovic
Date: Sat, 31 Dec 2005 17:20
Date: Sat, 31 Dec 2005 17:20
65 lines
3223 bytes
3223 bytes
> // Globals char *bf, int wh=0; and bool dun=false; to begin with.
> //---------------------------------------------------------------------------
> void __fastcall TForm1::ClientSocket1Read(TObject *Sender,TCustomWinSocket
> *Socket)
> {
> int ii,jj=Socket->ReceiveLength();
> while (jj>0)
> {
> ii=Socket->ReceiveBuf(&bf[wh],jj);
> wh+=ii; bf[wh]='\0'; Sleep(200); jj=Socket->ReceiveLength();
> }
> dun=true;
> }
This code (IMO) is not reliable, although it may work for some time.
The presented concept relies on the assumption that *all* the buffer will
be received in *one* pass, ie on *one* ClientSocket1Read().
In practice, max 8192 really will be sent to: jj=Socket->ReceiveLength(),
or less if the header is smaller that 8192, which it is most of the time.
But, the buffer of 8192 is not guaranteed at all to be sent.
In real world, *one* SEND (on server side) is not *one* RECEIVE
(on client side). The data is SENT or RECEIVED in *portions*.
AFAIK, it's the responsibility of YOUR code to *finalize* the block
of code which is received. Typically, the client & server must nagotiate
about the protocol, for example
buffer_length BUFFER
...so when staring receiving, the server (or client in your case) reads
those
4 bytes (buffer_length) and knows how much data is to be received. Also
sometimes, OnDisconnect can be the initiator to finalize the block of data.
Now, I will post you the *working code* example for this scenario:
1) client asks the server if autoexec.bat exeists on server machine
2) server receives request
3) server decodes request
4) server sends client the answer
5) client receives answer
6) client shows the message
In this example, the point 5, is ClientSocket1Read(), which
you are interested in.
P.S.
Also, I strongly suggest you (in the future) to look at Indy
set of components which is MUCH more easier for usage,
and does the threading for you internaly, and may alredy have
you task solved in the presented components.
--
Best regards,
Vladimir Stefanovic
Re: Non-blocking socket - how to detect when server has finished writing to client
Author: Mark Jacobs
Date: Sat, 31 Dec 2005 22:01
Date: Sat, 31 Dec 2005 22:01
26 lines
2067 bytes
2067 bytes
"Vladimir Stefanovic" <antivari@po.sbb.co.yu> wrote on Sat, 31 Dec 2005 18:20:58 +0100 :-
> 5) client receives answer
I have found a way round it. In my app, a POP3 mail lister for heavily-spammed accounts, the reply to all POP3
commands is either very short, or multi-line and terminated with a line starting with a full stop. In the long
requests like RETR and TOP x y, I stop waiting to read any more when I get this line in my buffer. It works a treat
for a single-threaded VCL app, with the occasional hiccup. Here is my revised code :-
// Globals int wh (length of the buffer so far), char *bf (buffer)
// bool needot (wait until a dot is recvd.), bool dun ("got everything from server" flag)
void __fastcall TForm1::ClientSocket1Read(TObject *Sender,TCustomWinSocket *Socket)
{
int ii=1;
while (ii>0)
{
ii=Socket->ReceiveBuf(&bf[wh],2048);
wh+=ii; bf[wh]='\0'; if (ii>0) Sleep(10);
}
dun=(!needot || AnsiString(bf).Pos("\r\n.")>0);
}
Now I need to add in the spam filtering, and decode attachments embedded in the output of RETR. Isn't there a
free VCL add-on to decode attachments somewhere? Also, is 2048 a good read value to use?
Mark Jacobs
Re: Non-blocking socket - how to detect when server has finished writing to client
Author: Vladimir Stefanovic
Date: Sun, 01 Jan 2006 07:43
Date: Sun, 01 Jan 2006 07:43
92 lines
3776 bytes
3776 bytes
Hi Mark,
> [...] the reply to all POP3 commands is either very short, or multi-line
> and terminated with a line starting with a full stop. In the long requests
> like
> RETR and TOP x y, I stop waiting to read any more when I get this line
> in my buffer.
Yes you must know the rules (protocol) to finalize you block of received
data.
if "AnsiString(bf).Pos("\r\n.")>0" is the rule, than OK.
> [...] Also, is 2048 a good read value to use?
Yes it is, but you *must not* count on that.
I already posted for you in attachments group the whole
TClientSocket/TServerSocket
code, which has all elements of ctNonBlocking / stBlocking architecture.
This is PSEUDO, code, what you have to do:
void __fastcall TClientForm::AskClientSocketRead(TObject *Sender,
TCustomWinSocket *Socket)
{
// read the incoming size of data that are passed to client...
int IncomingSize = Socket->ReceiveLength();
// if its less than zero, feel free to return and wait for another try...
if ( IncomingSize < 1 )
return;
// calculate the LAST size of your data buffer where you store the
portions...
// ... sent to client to finalize one logical block of data...
int OldSize = DataBuffer->Size;
// set the new position where the new reading start...
DataBuffer->Position = OldSize;
// calculate the NEW size of DataBuffer
int NewSize = OldSize + IncomingSize;
// set the new size of the buffer, so that we can work with allocated
buffer
DataBuffer->Size = NewSize;
// this will read the sent data into DataBuffer
Socket->ReceiveBuf( &((LPBYTE)(DataBuffer->Memory))[OldSize],
IncomingSize );
// now, check if it's the end of header...
if ( /* somethng with Pos("\r\n.") on dataBuffer* / )
{
// if yes, then:
// 1) place the DataBuffer in your Grid
// 2) reset the DataBuffer's Size & Position
// DataBuffer->Size = NewSize;
// DataBuffer->Position = 0;
}
}
...and this is DataBuffer:
//---------------------------------------------------------------------------
__fastcall TClientForm::TClientForm(TComponent* Owner)
: TForm(Owner)
{
DataBuffer = new TMemoryStream();
DataBuffer->Clear();
DataBuffer->Position = 0;
}
__fastcall TClientForm::~TClientForm()
{
delete DataBuffer;
}
Look at my code in attachemnts group.
Also you may look at the generic code for all sending amd receiving of
Remy Lebeau, which is placed many times before in this group:
http://tinyurl.com/dkbe9
Happy New Year
--
Best regards,
Vladimir Stefanovic
Re: Non-blocking socket - how to detect when server has finished writing to client
Author: Mark Jacobs
Date: Wed, 04 Jan 2006 12:12
Date: Wed, 04 Jan 2006 12:12
119 lines
5592 bytes
5592 bytes
Vladimir Stefanovic wrote:
> Hi Mark,
>
>
>>[...] the reply to all POP3 commands is either very short, or multi-line
>>and terminated with a line starting with a full stop. In the long requests
>>like
>>RETR and TOP x y, I stop waiting to read any more when I get this line
>>in my buffer.
>
>
> Yes you must know the rules (protocol) to finalize you block of received
> data.
> if "AnsiString(bf).Pos("\r\n.")>0" is the rule, than OK.
>
>
>>[...] Also, is 2048 a good read value to use?
>
>
> Yes it is, but you *must not* count on that.
>
> I already posted for you in attachments group the whole
> TClientSocket/TServerSocket
> code, which has all elements of ctNonBlocking / stBlocking architecture.
>
> This is PSEUDO, code, what you have to do:
>
> void __fastcall TClientForm::AskClientSocketRead(TObject *Sender,
> TCustomWinSocket *Socket)
> {
> // read the incoming size of data that are passed to client...
> int IncomingSize = Socket->ReceiveLength();
>
> // if its less than zero, feel free to return and wait for another try...
> if ( IncomingSize < 1 )
> return;
>
> // calculate the LAST size of your data buffer where you store the
> portions...
> // ... sent to client to finalize one logical block of data...
> int OldSize = DataBuffer->Size;
>
> // set the new position where the new reading start...
> DataBuffer->Position = OldSize;
>
> // calculate the NEW size of DataBuffer
> int NewSize = OldSize + IncomingSize;
>
> // set the new size of the buffer, so that we can work with allocated
> buffer
> DataBuffer->Size = NewSize;
>
> // this will read the sent data into DataBuffer
> Socket->ReceiveBuf( &((LPBYTE)(DataBuffer->Memory))[OldSize],
> IncomingSize );
>
> // now, check if it's the end of header...
> if ( /* somethng with Pos("\r\n.") on dataBuffer* / )
> {
> // if yes, then:
> // 1) place the DataBuffer in your Grid
> // 2) reset the DataBuffer's Size & Position
> // DataBuffer->Size = NewSize;
> // DataBuffer->Position = 0;
> }
>
> }
>
> ...and this is DataBuffer:
>
> //---------------------------------------------------------------------------
> __fastcall TClientForm::TClientForm(TComponent* Owner)
> : TForm(Owner)
> {
> DataBuffer = new TMemoryStream();
> DataBuffer->Clear();
> DataBuffer->Position = 0;
> }
> __fastcall TClientForm::~TClientForm()
> {
> delete DataBuffer;
> }
>
> Look at my code in attachemnts group.
>
> Also you may look at the generic code for all sending amd receiving of
> Remy Lebeau, which is placed many times before in this group:
> http://tinyurl.com/dkbe9
>
> Happy New Year
>
Thanks Vladimir. I discovered that putting Sleep in the OnClientRead routine is a calamity
waiting to happen. When comms locks up, so does the app! I have now got it perfect. And I
understand exactly what is happening and why. My code now looks like this :-
//---------------------------------------------------------------------------
void __fastcall TForm1::ClientSocket1Read(TObject *Sender,TCustomWinSocket *Socket)
{
int ii=Socket->ReceiveLength();
ii=Socket->ReceiveBuf(&bf[wh],ii); wh+=ii; bf[wh]='\0';
dun=(!needot || AnsiString(bf).Pos("\r\n.\r\n")>0);
}
//---------------------------------------------------------------------------
I also found that sending a NOOP to the server every second, whilst waiting for the data
to be sent, stops the server "forgetting about you" and closing the connection. I now have
a superb bulk email handling program, which deals with my 10,000 spam emails a day
problem, and allows me to fine tune spam filters and whitelists. It's a bit like JBMail in
look, but JBMail tries to do too much IMO, and charges you for the full version. I may
make this offering free on my website, like MJ Registry Watcher. Keep your eyes peeled for
new additions to my free software page at http://www.jacobsm.com/mjsoft.htm
Now, back to my question about a VCL component that is free of charge, and understands how
to decode the attachments out of a text file returned by a POP3 call to RETR.
--
Mark Jacobs
http://www.dkcomputing.co.uk
Thread Navigation
This is a paginated view of messages in the thread with full content displayed inline.
Messages are displayed in chronological order, with the original post highlighted in green.
Use pagination controls to navigate through all messages in large threads.
Back to All Threads