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:"David Allen" (dwall..@triosoftinc.com)
Date:Fri, 12 May 2006 15:26:21

Thank you, great examples.

"Remy Lebeau (TeamB)" <no.spam@no.spam.com> wrote in message
news:44619c12@newsgroups.borland.com...
>
> "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:

none

In response to:

www.cryer.info
Managed Newsgroup Archive