Newsgroups : Borland : borland.public.delphi.internet.winsock : 2008 Jan : Re: TClientSocket write progress???

www.cryer.info
Managed Newsgroup Archive

Re: TClientSocket write progress???

Subject:Re: TClientSocket write progress???
Posted by:"Remy Lebeau (TeamB)" (no.spam@no.spam.com)
Date:Thu, 24 Jan 2008 11:38:44

"Bo Berglund" <bo.berglund@telia.com> wrote in message
news:2ebgp3lj3mjr0t7f8g7hsvuhpj80u762al@4ax.com...

> Something is not right here...
> I have written the send part of my program this way (the repeat loop
> was put there only yesterday after reading your responses):

You did not pay attention to everything I told you.  If SendText()
returns -1, you MUST wait for the OnWrite event to be triggered before you
can write more data to the socket.  You are not checking for that at all.
And since you are using a non-blocking socket, you MUST allow window
messages to be processed as well, or else the event will never be triggered.

Did you try looking at the newsgroup archives for examples, like I
suggested?  For instance:

    http://groups.google.com/group/borland.public.delphi.internet.winsock/msg/af50cfbfc40fb5e9
    (you would have to adapt it to your STX-ETX model, but you should get
the gist of how to manage the OnWrite event in general)

> function TSSRemoteClient.SendTextData(Msg: string): boolean;

If you must wait for all of the data to be written before exiting, then you
have to code it more like this instead:

    function TSSRemoteClient.SendTextData(const Msg: string): boolean;
    var
        TxLen: integer;
        Tel: string;
    begin
        Result := False;
        try
            if FSocket.Socket.Connected then
            begin
                LogStd('Tx to ' + FSocket.Socket.RemoteAddress + ': ' +
Msg);
                FPacketCount := 0;
                Tel := STX + Msg + ETX;
                repeat
                    TxLen := FSocket.Socket.SendText(Tel);
                    if TxLen < 0 then // socket would block
                    begin
                        Application.ProcessMessages; // allow OnWrite to be
processed
                        Continue;
                    end;
                    if TxLen = 0 then
                    begin
                        LogErr('Disconnected from host, cannot send data');
                        Exit;
                    end;
                    Delete(Tel, 1, TxLen);
                until Length(Tel) = 0;
                Result := true;
            end
            else
                LogErr('Disconnected from host, cannot send data');
        except
            on E: Exception do
                LogErr('Exception in SendTextData: ' + E.Message);
        end;
    end;

Otherwise, use blocking sockets instead.

> Before this I just used
>  FSocket.Socket.SendText(Tel);
> and no repeat loop.

Which did not protect your data in any way from partial transmissions or
errors.

> When sending a file I load it fully into memory

I do not recommend you do that.  It would be better to read it in smaller
chunks, sending each one separately, ie

    open file
    repeat
        read a chunk
        send it
        update progress
    until no more data
    close file

> then make a hex conversion of every single byte in the file body
> (blowing it up by x2) to make the data ASCII.

Since hex conversion will bloat the data by 2x, you should consider using
Base64, yEnc, or other encoding scheme that has better compression in it.
Or, if you can avoid using STX and ETX then you could just send the raw
binary data as-is without encoding it at all.

> I have not ever gotten a truncated file sent.

That doesn't mean it couldn't happen, though.  Large files, or slow
networks, would be susceptable to the conditions I described earlier.  Your
earlier code was simply not up to the task of handling everything properly.

> And I have tested with files more than several megabytes in size.
> And I have also tested to see what happens in this area of the code
> while stepping through. The SendText method always returns
> *immediately* at the same time as network activity starts and
> continues for a long time.

SendText() does a single call to SendBuf() and returns whatever SendBuf()
returns:

    function TCustomWinSocket.SendText(const s: string): Integer;
    begin
        Result := SendBuf(Pointer(S)^, Length(S));
    end;

SendBuf() can only send however much the socket can physically hold at one
time.  There is no looping inside of SendBuf().  You cannot pass megabytes
of data in a single call.  It is not physically possible.  And because of
the non-blocking nature of your sockets, SendBuf() is not guaranteed to even
be able to accept data at any arbitrary time, either.  The Result will be -1
(SOCKET_ERROR) instead (if any error other than WSAEWOULDBLOCK is reported,
you will also get an OnError event).

> This wait takes maybe 20 s for a large file and here is where I want
> to have the progress info.

Simply put it inside the loop, ie:

    function TSSRemoteClient.SendTextData(const Msg: string): boolean;
    var
        TxLen, TotalLen, LenSent: integer;
        Tel: string;
    begin
        Result := False;
        try
            if FSocket.Socket.Connected then
            begin
                LogStd('Tx to ' + FSocket.Socket.RemoteAddress + ': ' +
Msg);
                FPacketCount := 0;
                Tel := STX + Msg + ETX;
                TotalLen := Length(Tel);
                LenSent := 0;
                repeat
                    TxLen := FSocket.Socket.SendText(Tel);

                    if TxLen < 0 then // socket would block
                    begin
                        // must allow OnWrite to be processed before writing
more data...
                        Application.ProcessMessages;
                        Continue;
                    end;

                    if TxLen = 0 then
                    begin
                        // other party disconnected...
                        LogErr('Disconnected from host, cannot send data');
                        Exit;
                    end;

                    // at least 1 byte was sent...
                    Delete(Tel, 1, TxLen);

                    Inc(LenSent, TxLen);
                    // update/display progress as needed. You have the total
length being
                    // sent, and the length that has been sent so far.  You
can calculate a
                    // percentage from those two values if desired.
Remember to call
                    // Application.ProcessMessages as well if you are
updating the UI...

                until LenSent >= TotalLen;
                Result := true;
            end
            else
                LogErr('Disconnected from host, cannot send data');
        except
            on E: Exception do
                LogErr('Exception in SendTextData: ' + E.Message);
        end;
    end;

> But your suggestion would mean having the progress go from 0 to 100%
> instantaneously when the data are still only starting to being
> transfered.

No, it would not.

> Remember that my post was not about how to stop losing data, it was
> on getting a transfer progress display while successfully sending the
> data.

I realize that.  But you can't implement progress tracking in this situation
without looping, since TClientSocket does not have any progress
notifications of its own.  Since you have to loop anyway, you may as well
also learn the proper way to manage the loop itself.

> As I said I have never once had a bad transfer

Then you got lucky.  Your earlier code was still prone to errors.  Even your
newer looping code still has some room for errors in it.


Gambit

Replies:

In response to:

www.cryer.info
Managed Newsgroup Archive