Newsgroups : Borland : borland.public.delphi.internet.winsock : 2006 May : Re: TServerSocket.onClientRead buffer under/overflow

www.cryer.info
Managed Newsgroup Archive

Re: TServerSocket.onClientRead buffer under/overflow

Subject:Re: TServerSocket.onClientRead buffer under/overflow
Posted by:"Remy Lebeau (TeamB)" (no.spam@no.spam.com)
Date:Wed, 10 May 2006 00:51:05

"David Allen" <dwallen@triosoftinc.com> wrote in message
news:44613ab1$1@newsgroups.borland.com...

> In that .01% of the time the OnClientRead gets triggered when either
> an incomplete records has been received in the buffer or more than
> one record is in the buffer.

That is very common in socket programming, and perfectly normal.  Your
OnRead event handler must be coded to handle all three of the following
scenerios or else your communication with the client can become corrupted:

1) the event is triggered for 1 complete record only

2) the event is triggered for 1 incomplete record only

3) the event is triggered for multiple records, either complete or
incomplete, or both mixed

> For the 'more than one record in the buffer' I have the program
recognizing
> the buffer is larger than it needed to be and deleting the first x number
of
> characters and goes back and processes the remainder.

That is not the correct way to handle it.  You should not be discarding
anything.  You need to manage your own in-memory buffer of the incoming data
instead.  Place all incoming data into that buffer, and then run through as
many complete records in your buffer as you can.  When you detect an
incomplete record, then stop running through the buffer, leave the
incomplete data in it, and exit the event handler.  The next time the event
is triggered, append the new incoming data to the previous data in the
buffer, and then run through the buffer looking for complete records again.

You are also not taking into account that sending outbound data (in your
case, when calling SendText()) can also be broken up into smaller pieces,
and multiple pieces concatentated together.  You need to look at the return
value of SendText() in order to know how many bytes are actually sent, and
then buffer any unsent bytes.  You then have to wait for the OnClientWrite
event to be triggered before you can try resending the pending bytes, or any
new bytes.

Sample code for doing that kind of management has been posted many many
times before.  Go to http://www.deja.com to search through the newsgroup
archives for detailed explanations.

For example (untested):

    type
        TSocketBuffers = class(TObject)
        private
            FInbound: TMemoryStream;
            FOutbound: TMemoryStream;
        public
            constructor Create;
            destructor Destroy; override;
            property Inbound: TMemoryStream read FInbound;
            property Outbound: TMemoryStream read FOutbound;
        end;

    constructor TSocketBuffer.Create;
    begin
        inherited;
        FInbound := TMemoryStream.Create;
        FOutbound := TMemoryStream.Create;
    end;

    destructor TSocketBuffer.Destroy;
    begin
        FInbound.Free;
        FOutbound.Free;
        inherited;
    end;

    procedure TfrmMain.ServerSocketClientConnect(Sender: TObject; Socket:
TCustomWinSocket);
    begin
        Socket.Data := TSocketBuffers.Create;
    end;

    procedure TfrmMain.ServerSocketClientDisconnect(Sender: TObject; Socket:
TCustomWinSocket);
    begin
        TSocketBuffers(Socket.Data).Free;
        Socket.Data := nil;
    end;

    procedure TfrmMain.ServerSocketClientWrite(Sender: TObject; Socket:
TCustomWinSocket);
    var
        Ptr: PByte;
        Count: Integer;
    begin
        with TSocketBuffers(Socket.Data).Outbound do
        begin
            if Size = 0 then Exit;

            Seek(0, soFromBeginning);
            Ptr := PByte(Memory);

            while Position < Size do
            begin
                Count := Socket.SendBuf(Ptr, Size - Position);
                if Count < 1 then Break;
                Seek(Count, soFromCurrent);
                Inc(Ptr, Count);
            end;

            if Position = 0 then Exit;

            Count := Size - Position;
            if Count > 0 then
            begin
                Move(Ptr, Memory, Count);
                Size := Count;
            end else
                Clear;
        end;
    end;

    procedure TfrmMain.ServerSocketClientRead(Sender: TObject; Socket:
TCustomWinSocket);
    var
        Count, OldSize: Integer;
        Ptr: PByte;
        Request, Response: String;
        I: Integer;
    begin
        Count := Socket.ReceiveLength;
        if Count < 1 then Exit;

        with TSocketBuffers(Socket.Data).Inbound do
        begin
            OldSize := Size;
            Size := OldSize + Count;

            Ptr := PByte(Memory);
            Inc(Ptr, OldSize);

            Count := Socket.ReceiveBuf(Ptr, Count);
            if Count < 1 then
            begin
                Size := OldSize;
                Exit;
            end;

            Size := OldSize + Count;

            Seek(0, soFromBeginning);
            Ptr := PByte(Memory);

            while Position < Size do
            begin
                Count := Size - Position;
                if Count < 6 then Break;

                if StrLComp(PChar(Ptr), 'GETZIP', 6) = 0 then
                begin
                    if Count < 22 then Break;

                    SetString(Request, PChar(Ptr), 22);
                    Seek(22, soFromCurrent);
                    Inc(Ptr, 22);

                    Response := ProcessGetZip(Request, LogZ);
                    SendString(Socket, Response);
                    LogRequest(Request, Response, LogZ);

                    Continue;
                end;

                if StrLComp(PChar(Ptr), 'GETTAX', 6) = 0 then
                begin
                    if Count < 89 then Break;

                    SetString(Request, PChar(Ptr), 89);
                    Seek(89, soFromCurrent);
                    Inc(Ptr, 89);

                    Response := ProcessGetTax(Request, LogT);
                    SendString(Socket, Response);
                    LogRequest(Request, Response, LogT);

                    Continue;
                end;

                for I := 1 to Count-1 do
                begin
                    if Ptr[I]^ = 'G' then Break;
                end;

                SetString(Request, PChar(Ptr), I);
                Seek(I, soFromCurrent);
                Inc(Ptr, I);

                Response := 'Response to unknown request: ' + Request;
                LogRequest(Request, Response, Log);

                Break;
            end;

            if Position > 0 then
            begin
                Count := Size - Position;
                if Count > 0 then
                begin
                    Move(Ptr, Memory, Count);
                    Size := Count;
                end else
                    Clear;
            end;
        end;
    end;

    function TfrmMain.SendData(Socket: TCustomWinSocket; var Buffer;
BufSize: Integer): Boolean;
    var
        Ptr: PByte;
        Sent: Integer;
    begin
        Result := False;
        Ptr := PByte(Buffer);

        with TSocketBuffers(Socket.Data).Outbound do
        begin;
            if Size = 0 then
            begin
                while BufSize > 0 do
                begin
                    Sent := Socket.SendBuf(Ptr, BufSize);
                    if Sent > 0 then
                    begin
                        Inc(Ptr, Sent);
                        Dec(BufSize, Sent);
                        if BufSize = 0 then
                        begin
                            Result := True;
                            Exit;
                        end;
                    end;
                    if (Sent = 0) or (WSAGetLastError <> WSAEWOULDBLOCK)
then Exit;
                    Break;
                end;
            end;
            Seek(0, soFromEnd);
            WriteBuffer(Ptr, BufSize);
            Result := True;
        end;
    end;

    function TfrmMain.SendString(Socket: TCustomWinSocket; const S: String):
Boolean;
    begin
        Result := SendData(Socket, PChar(S), Length(S));
    end;


> There has to be a better way because once it starts the too little/too
> much routine I start dropping reply's or sending the wrong reply such
> as the ZIP Code is invalid or the tax is zero.

That is because you are not managing the socket properly to begin with.


Gambit

Replies:

In response to:

www.cryer.info
Managed Newsgroup Archive