Newsgroups : Borland : borland.public.delphi.internet.winsock : 2006 May : 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
none