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