 | 
|
|
|
{$INCLUDE ..\cDefines.inc}
unit cSocketsTCP;
{ }
{ TCP socket class v3.05 }
{ }
{ This unit is copyright © 2001-2004 by David J Butler }
{ }
{ This unit is part of Delphi Fundamentals. }
{ Its original file name is cSocketsTCP.pas }
{ The latest version is available from the Fundamentals home page }
{ http://fundementals.sourceforge.net/ }
{ }
{ I invite you to use this unit, free of charge. }
{ I invite you to distibute this unit, but it must be for free. }
{ I also invite you to contribute to its development, }
{ but do not distribute a modified copy of this file. }
{ }
{ A forum is available on SourceForge for general discussion }
{ http://sourceforge.net/forum/forum.php?forum_id=2117 }
{ }
{ }
{ Revision history: }
{ 11/12/2001 2.01 Initial version of AClientSocket, TClientSocket. }
{ 24/08/2002 3.02 Created cSocketsTCP from cSocketsTCPClient. }
{ 18/12/2002 3.03 Changed ReadStr to also read directly from the Windows }
{ socket buffer. This is more efficient when ReadStr is }
{ called from the OnDataAvailable handler. }
{ 06/06/2003 3.04 Fixed bug in Peek function. }
{ 16/10/2003 3.05 Added InBufferMaxSize property. This sets a limit on the }
{ size of the data read into the incoming buffer. }
{ }
interface
uses
{ Delphi }
WinSock,
{ Fundamentals }
cUtils,
cSockets;
{ }
{ ATCPClientSocket }
{ Base class for TCP client sockets. }
{ Implements read, write and close functionality. }
{ }
type
ATCPClientSocket = class;
ATCPClientSocketEvent = procedure (Sender: ATCPClientSocket) of object;
ATCPClientSocket = class(ASocketExSendBuffer)
protected
FInBuffer : String;
FInBufferSize : Integer;
FInBufferPos : Integer;
FInBufferMaxSize : Integer;
FDataToRead : Boolean;
FThrottleRead : Boolean;
FReadThrottleRate : Integer;
FReadTimerStart : Integer;
FReadTimerCount : Integer;
FReadRate : Integer;
FGotRemoteHostName : Boolean;
FRemoteHostName : String;
FOnDataAvailable : ATCPClientSocketEvent;
FOutBufferSize : Integer;
FClosePending : Boolean;
FOnClose : ATCPClientSocketEvent;
procedure Init; override;
procedure Init(const SocketHandle: TSocket); override;
procedure WMSocket(const Events, lWordHi: Word); override;
function GetAsynchronousEvents: LongInt; override;
procedure SetLingerOption(const LingerOption: Boolean);
function ThrottleTimerActive: Boolean; override;
procedure TriggerTimer; override;
procedure TriggerSendBufferEmpty; override;
procedure HandleReadEvent;
procedure HandleCloseEvent(const Error: Word);
procedure HandleDataToRead;
procedure SetInBufferMaxSize(const InBufferMaxSize: Integer);
procedure PackInBuffer;
procedure AdvanceInBuffer(const Count: Integer);
procedure SetThrottleRead(const ThrottleRead: Boolean);
function SocketAvailableToRead: Integer;
function SocketRead(var Buf; const Size: Integer; var Error: Integer): Integer;
function ReadFromBuffer(var Buf; const Size: Integer; const Peek: Boolean): Integer;
function SkipBuffered(const Count: Integer): Integer;
function BufferInData(const Count: Integer; var Error: Integer): Integer;
procedure TriggerDataAvailable; virtual;
function SocketSend(const Buf; const Size: Integer; var Error: Integer): Integer;
function SendData(const Buf; const Size: Integer; var Error: Integer): Integer;
procedure SendBufferedData; override;
procedure TriggerClose; virtual;
procedure SetClosed;
public
procedure Close(const Wait: Boolean);
function Connected: Boolean;
function GetRemoteAddress: TInAddr; virtual;
function GetRemoteAddressStr: String; virtual;
function GetRemoteHostName: String; virtual;
function Read(var Buf; const Size: Integer): Integer;
function ReadStr(const Size: Integer): String;
function ReadAvailable: String;
function AvailableToRead: Integer;
property InBufferSize: Integer read FInBufferSize;
property InBufferMaxSize: Integer read FInBufferMaxSize write SetInBufferMaxSize default -1;
property ThrottleRead: Boolean read FThrottleRead write SetThrottleRead;
property ReadThrottleRate: Integer read FReadThrottleRate write FReadThrottleRate;
function GetReadRate: Integer;
function GetTransferRate: Integer;
function Send(const Buf; const Size: Integer): Integer;
function SendStr(const Buf: String): Integer;
property OutBufferSize: Integer read FOutBufferSize;
function Peek(var Buf; const Size: Integer): Integer;
function PeekStr(const Size: Integer): String;
function PeekAvailable: String;
function Skip(const Count: Integer): Integer;
function LocateChar(const Ch: Char; const LocateNonMatch: Boolean = False): Integer; overload;
function LocateChar(const C: cUtils.CharSet; const LocateNonMatch: Boolean = False): Integer; overload;
function LocateSequence(const Sequence: String; const CaseSensitive: Boolean = True): Integer;
function MatchSequence(const Sequence: String; const CaseSensitive: Boolean = True): Boolean;
function MatchChar(const Ch: Char; const MatchNonMatch: Boolean = False;
const SkipOnMatch: Boolean = False): Boolean;
function IsLineAvailable: Integer;
function ReadLine(var Line: String): Boolean;
property OnDataAvailable: ATCPClientSocketEvent read FOnDataAvailable write FOnDataAvailable;
property OnClose: ATCPClientSocketEvent read FOnClose write FOnClose;
end;
implementation
uses
{ Delphi }
Windows,
{ Fundamentals }
cStrings,
cLinkedLists,
cWinSock;
{ }
{ ATCPClientSocket }
{ }
type
TTCPSendBufferItem = class(TDoublyLinkedString);
procedure ATCPClientSocket.Init;
begin
inherited Init;
FProtocol := spTCP;
FInBufferMaxSize := -1;
end;
procedure ATCPClientSocket.Init(const SocketHandle: TSocket);
var L : Integer;
begin
inherited Init(SocketHandle);
if cWinSock.ioctlsocket(SocketHandle, FIONREAD, L) >= 0 then
begin
FState := ssConnected;
SetSocketAsynchronous;
FReadTimerStart := GetTickCount;
FReadTimerCount := 0;
end;
end;
procedure ATCPClientSocket.WMSocket(const Events, lWordHi: Word);
begin
if Events and FD_CLOSE <> 0 then
HandleCloseEvent(lWordHi);
if Events and FD_READ <> 0 then
HandleReadEvent;
inherited WMSocket(Events, lWordHi);
end;
function ATCPClientSocket.GetAsynchronousEvents: LongInt;
begin
Result := FD_READ or FD_WRITE or FD_CLOSE or FD_CONNECT;
end;
procedure ATCPClientSocket.SetLingerOption(const LingerOption: Boolean);
var Opt : LongBool;
begin
Opt := LingerOption;
if cWinSock.setsockopt(GetSocketHandle, SOL_SOCKET, SO_LINGER, PChar(@Opt), Sizeof(Opt)) <> 0 then
RaiseLastWinSockError('Socket linger option not set');
end;
function ATCPClientSocket.Connected: Boolean;
begin
Result := FState = ssConnected;
end;
function ATCPClientSocket.GetReadRate: Integer;
var Elapsed : Integer;
begin
AdvanceTimer(FReadTimerStart, FReadTimerCount, GetTickCount, Elapsed, FReadRate);
Result := FReadRate;
end;
function ATCPClientSocket.GetTransferRate: Integer;
begin
Result := GetReadRate + GetWriteRate;
end;
{ Throttle }
procedure ATCPClientSocket.SetThrottleRead(const ThrottleRead: Boolean);
begin
if ThrottleRead = FThrottleRead then
exit;
FThrottleRead := ThrottleRead;
RefreshThrottleTimer;
if not ThrottleRead and (FState = ssConnected) then
PostMessage(FWindowHandle, WM_SOCKET, FSocketHandle, FD_READ); // Re-start unthrottled handling
end;
function ATCPClientSocket.ThrottleTimerActive: Boolean;
begin
Result := (FThrottleRead or FThrottleWrite) and (FState = ssConnected);
end;
procedure ATCPClientSocket.TriggerTimer;
begin
inherited TriggerTimer;
if FThrottleRead then
HandleReadEvent;
end;
{ Close }
procedure ATCPClientSocket.TriggerClose;
begin
if Assigned(FOnClose) then
FOnClose(self);
end;
procedure ATCPClientSocket.SetClosed;
begin
SetState(ssClosed);
TriggerClose;
DestroySocketHandle;
ClearSendBuffer;
end;
procedure ATCPClientSocket.HandleCloseEvent(const Error: Word);
begin
HandleReadEvent; // Read unfired data from socket buffers before closing
SetError(Error, '');
SetClosed;
end;
procedure ATCPClientSocket.Close(const Wait: Boolean);
begin
if (FState = ssClosed) or (FSocketHandle = INVALID_SOCKET) then
exit;
cWinSock.shutdown(FSocketHandle, SD_SEND);
if Wait and not FOutBuffer.IsEmpty then
FClosePending := True
else
SetClosed;
end;
function ATCPClientSocket.GetRemoteAddress: TInAddr;
var Addr : TSockAddrIn;
Len : Integer;
begin
CheckSocketConnected;
Len := Sizeof(Addr);
if cWinSock.getpeername(FSocketHandle, Addr, Len) <> 0 then
Result.S_addr := INADDR_NONE else
Result := Addr.sin_addr;
end;
function ATCPClientSocket.GetRemoteAddressStr: String;
var Address : TInAddr;
begin
Address := GetRemoteAddress;
if (Address.S_addr = INADDR_NONE) or (Address.S_addr = INADDR_ANY) then
Result := '' else
Result := IPAddressStr(Address);
end;
function ATCPClientSocket.GetRemoteHostName: String;
var Address : TInAddr;
begin
if not FGotRemoteHostName then
begin
Address := GetRemoteAddress;
if (Address.S_addr = INADDR_NONE) or (Address.S_addr = INADDR_ANY) then
FRemoteHostName := '' else
begin
FRemoteHostName := cWinSock.GetRemoteHostName(Address);
if FRemoteHostName = '' then
FRemoteHostName := IPAddressStr(Address);
end;
FGotRemoteHostName := True;
end;
Result := FRemoteHostName;
end;
{ Read }
function ATCPClientSocket.SocketAvailableToRead: Integer;
begin
if FSocketHandle = INVALID_SOCKET then
Result := 0
else
if cWinSock.ioctlsocket(FSocketHandle, FIONREAD, Result) <> 0 then
Result := 0
else
if FThrottleRead then
Result := ThrottledSize(FReadTimerStart, FReadTimerCount, Result,
GetTickCount, FReadThrottleRate, FReadRate);
end;
procedure ATCPClientSocket.TriggerDataAvailable;
begin
if Assigned(FOnDataAvailable) then
FOnDataAvailable(self);
end;
function ATCPClientSocket.SocketRead(var Buf; const Size: Integer;
var Error: Integer): Integer;
var L : Integer;
begin
if Size <= 0 then
begin
Error := 0;
Result := 0;
exit;
end;
if FThrottleRead and (FState = ssConnected) then
begin
L := ThrottledSize(FReadTimerStart, FReadTimerCount, Size,
GetTickCount, FReadThrottleRate, FReadRate);
if L = 0 then
begin
Error := 0;
Result := 0;
exit;
end;
end
else
L := Size;
Result := cWinSock.recv(FSocketHandle, Buf, L, 0);
if Result < 0 then
begin
Error := cWinSock.WSAGetLastError;
Result := 0;
end
else
begin
Error := 0;
Inc(FReadTimerCount, Result);
end;
end;
procedure ATCPClientSocket.PackInBuffer;
var L : Integer;
P : PChar;
begin
L := Length(FInBuffer);
// prevent large empty buffers from hanging around
// prevent unused area from growing out of control
if (L >= 16384) and ((FInBufferSize < 256) or (FInBufferPos > L div 2)) then
begin
if FInBufferSize > 0 then
begin
P := Pointer(FInBuffer);
Inc(P, FInBufferPos);
Move(P^, Pointer(FInBuffer)^, FInBufferSize);
end;
FInBufferPos := 0;
SetLength(FInBuffer, MaxI(16384, FInBufferSize));
end;
end;
procedure ATCPClientSocket.AdvanceInBuffer(const Count: Integer);
begin
if Count <= 0 then
exit;
Assert(FInBufferSize >= Count, 'AdvanceBuffer: Invalid Count parameter');
Assert(FInBufferPos + FInBufferSize <= Length(FInBuffer),
'AdvanceBuffer: Invalid Buffer state');
Inc(FInBufferPos, Count);
Dec(FInBufferSize, Count);
PackInBuffer;
end;
function ATCPClientSocket.ReadFromBuffer(var Buf; const Size: Integer;
const Peek: Boolean): Integer;
var L : Integer;
P : PChar;
begin
Result := 0;
L := FInBufferSize;
if Size < L then
L := Size;
if L <= 0 then
exit;
P := Pointer(FInBuffer);
Inc(P, FInBufferPos);
Move(P^, Buf, L);
AdvanceInBuffer(L);
Result := L;
end;
function ATCPClientSocket.SkipBuffered(const Count: Integer): Integer;
var L : Integer;
begin
Result := 0;
L := FInBufferSize;
if Count < L then
L := Count;
if L <= 0 then
exit;
AdvanceInBuffer(L);
Result := L;
end;
function ATCPClientSocket.BufferInData(const Count: Integer;
var Error: Integer): Integer;
var L, I : Integer;
P : PChar;
begin
if Count <= 0 then
begin
Error := 0;
Result := 0;
exit;
end;
L := FInBufferPos + FInBufferSize;
I := L + Count;
if Length(FInBuffer) < I then
SetLength(FInBuffer, I + (I shr 2));
P := Pointer(FInBuffer);
Inc(P, L);
Result := SocketRead(P^, Count, Error);
if Result <= 0 then
exit;
Inc(FInBufferSize, Result);
end;
procedure ATCPClientSocket.HandleReadEvent;
var L, Error : Integer;
begin
// Give event handler a chance to read data first
L := SocketAvailableToRead;
if L > 0 then
TriggerDataAvailable;
// Buffer as much as possible from the socket
Repeat
if (FInBufferMaxSize >= 0) and (FInBufferSize >= FInBufferMaxSize) then
begin // Buffer full
FDataToRead := True;
exit;
end;
if L = 0 then
L := 1460; // Try anyway
if BufferInData(L, Error) <= 0 then
begin // No more data to read from socket
FDataToRead := False;
break;
end;
TriggerDataAvailable;
L := SocketAvailableToRead;
Until False;
end;
procedure ATCPClientSocket.HandleDataToRead;
begin
FDataToRead := False;
if FWindowHandle <> 0 then // Handle unread data
PostMessage(FWindowHandle, WM_SOCKET, FSocketHandle, FD_READ);
end;
procedure ATCPClientSocket.SetInBufferMaxSize(const InBufferMaxSize: Integer);
var R : Boolean;
begin
if InBufferMaxSize = FInBufferMaxSize then
exit;
R := FDataToRead and
(FInBufferMaxSize >= 0) and
(FInBufferSize >= FInBufferMaxSize) and
((InBufferMaxSize < 0) or (InBufferMaxSize > FInBufferMaxSize));
FInBufferMaxSize := InBufferMaxSize;
if R then
HandleDataToRead;
end;
// Reading of already received (buffered) data is allowed when State <> ssConnected.
function ATCPClientSocket.Read(var Buf; const Size: Integer): Integer;
var M, T, Error : Integer;
P : PChar;
begin
Result := 0;
if Size <= 0 then
exit;
// Read buffered data
P := @Buf;
T := Size;
M := ReadFromBuffer(P^, T, False);
if M > 0 then
begin
Inc(P, M);
Inc(Result, M);
Dec(T, M);
if FDataToRead then
HandleDataToRead;
if T <= 0 then
exit;
end;
// Read from socket
Repeat
M := SocketRead(P^, T, Error);
if M <= 0 then
exit else
begin
Inc(P, M);
Inc(Result, M);
Dec(T, M);
end;
Until T <= 0;
end;
function ATCPClientSocket.ReadStr(const Size: Integer): String;
var L, M : Integer;
begin
Result := '';
M := Size;
if M <= 0 then
exit;
if (FInBufferSize > 0) and (M >= FInBufferSize) then
begin
Result := ReadAvailable;
exit;
end;
SetLength(Result, M);
L := Read(Pointer(Result)^, M);
if L = M then
exit;
SetLength(Result, L);
end;
function ATCPClientSocket.Peek(var Buf; const Size: Integer): Integer;
var L : Integer;
P : PChar;
begin
Result := 0;
L := MinI(Size, FInBufferSize);
if L <= 0 then
exit;
P := Pointer(FInBuffer);
Inc(P, FInBufferPos);
Move(P^, Buf, L);
Result := L;
end;
function ATCPClientSocket.PeekStr(const Size: Integer): String;
var L, M : Integer;
begin
Result := '';
M := MaxI(0, Size);
if M = 0 then
exit;
if M >= FInBufferSize then
begin
Result := FInBuffer;
exit;
end;
SetLength(Result, M);
L := Peek(Pointer(Result)^, M);
if L = M then
exit;
SetLength(Result, L);
end;
function ATCPClientSocket.Skip(const Count: Integer): Integer;
begin
Result := SkipBuffered(Count);
if FDataToRead then
HandleDataToRead;
end;
function ATCPClientSocket.AvailableToRead: Integer;
begin
Result := FInBufferSize;
end;
function ATCPClientSocket.ReadAvailable: String;
begin
if FInBufferSize = 0 then
begin
Result := '';
exit;
end;
if FInBufferPos > 0 then
Delete(FInBuffer, 1, FInBufferPos);
if FInBufferSize < Length(FInBuffer) then
SetLength(FInBuffer, FInBufferSize);
Result := FInBuffer;
FInBufferPos := 0;
FInBufferSize := 0;
FInBuffer := '';
if FDataToRead then
HandleDataToRead;
end;
function ATCPClientSocket.PeekAvailable: String;
begin
if FInBufferSize = 0 then
begin
Result := '';
exit;
end;
if FInBufferPos > 0 then
Delete(FInBuffer, 1, FInBufferPos);
if FInBufferSize < Length(FInBuffer) then
SetLength(FInBuffer, FInBufferSize);
Result := FInBuffer;
FInBufferPos := 0;
end;
function ATCPClientSocket.LocateChar(const Ch: Char;
const LocateNonMatch: Boolean): Integer;
var I : Integer;
begin
if FInBufferSize = 0 then
begin
Result := -1;
exit;
end;
if LocateNonMatch then
I := PosNotChar(Ch, FInBuffer, FInBufferPos + 1)
else
I := PosChar(Ch, FInBuffer, FInBufferPos + 1);
if I > FInBufferPos + FInBufferSize then
I := 0;
if I >= 1 then
Result := I - FInBufferPos - 1
else
Result := -1;
end;
function ATCPClientSocket.LocateChar(const C: cUtils.CharSet;
const LocateNonMatch: Boolean): Integer;
var I : Integer;
begin
if FInBufferSize = 0 then
begin
Result := -1;
exit;
end;
if LocateNonMatch then
I := PosNotChar(C, FInBuffer, FInBufferPos + 1)
else
I := PosChar(C, FInBuffer, FInBufferPos + 1);
if I > FInBufferPos + FInBufferSize then
I := 0;
if I >= 1 then
Result := I - FInBufferPos - 1
else
Result := -1;
end;
function ATCPClientSocket.LocateSequence(const Sequence: String;
const CaseSensitive: Boolean): Integer;
var I : Integer;
begin
if (Sequence = '') or (FInBufferSize = 0) then
begin
Result := -1;
exit;
end;
I := PosStr(Sequence, FInBuffer, FInBufferPos + 1, CaseSensitive);
if I > FInBufferPos + FInBufferSize then
I := 0;
if I >= 1 then
Result := I - FInBufferPos - 1
else
Result := -1;
end;
function ATCPClientSocket.MatchSequence(const Sequence: String;
const CaseSensitive: Boolean): Boolean;
var L : Integer;
begin
L := Length(Sequence);
if (L = 0) or (FInBufferSize < L) then
begin
Result := False;
exit;
end;
if CaseSensitive then
Result := StrMatch(FInBuffer, Sequence, FInBufferPos + 1)
else
Result := StrMatchNoCase(FInBuffer, Sequence, FInBufferPos + 1);
end;
function ATCPClientSocket.MatchChar(const Ch: Char; const MatchNonMatch: Boolean;
const SkipOnMatch: Boolean): Boolean;
begin
if FInBufferSize < 1 then
begin
Result := False;
exit;
end;
Result := (FInBuffer[FInBufferPos + 1] = Ch) xor MatchNonMatch;
if Result and SkipOnMatch then
Skip(1);
end;
function ATCPClientSocket.IsLineAvailable: Integer;
begin
Result := LocateChar([#13, #10], False);
if Result < 0 then
exit;
if FInBufferSize < Result + 2 then
Result := -1;
end;
function ATCPClientSocket.ReadLine(var Line: String): Boolean;
var I : Integer;
C, D : Char;
begin
I := IsLineAvailable;
Result := I >= 0;
if not Result then
begin
Line := '';
exit;
end;
Line := ReadStr(I);
ReadFromBuffer(C, 1, False);
if Peek(D, 1) < 1 then
exit;
if ((C = #13) and (D = #10)) or ((C = #10) and (D = #13)) then
Skip(1);
end;
{ Send }
function ATCPClientSocket.SocketSend(const Buf; const Size: Integer;
var Error: Integer): Integer;
var L : Integer;
P : Pointer;
begin
L := GetThrottledWriteSize(Size);
if L = 0 then
begin
Result := 0;
Error := 0;
exit;
end;
P := @Buf;
Result := cWinSock.send(FSocketHandle, P^, L, 0);
ActSocketSent(Result, Error, Result);
end;
function ATCPClientSocket.SendData(const Buf; const Size: Integer;
var Error: Integer): Integer;
var P : PChar;
T, M : Integer;
begin
Result := 0;
Error := 0;
P := @Buf;
T := Size;
While T > 0 do
begin
M := SocketSend(P^, T, Error);
if M = 0 then
break;
Inc(Result, M);
Inc(P, M);
Dec(T, M);
end;
end;
procedure ATCPClientSocket.SendBufferedData;
var P : TTCPSendBufferItem;
L, Sent, Error : Integer;
Q : PChar;
begin
P := TTCPSendBufferItem(FOutBuffer.First);
While Assigned(P) do
begin
L := Length(P.Value);
Q := Pointer(P.Value);
Sent := SendData(Q^, L, Error);
if Sent = 0 then
exit;
Dec(FOutBufferSize, Sent);
if Sent < L then
begin
Inc(Q, Sent);
Move(Q^, Pointer(P.Value)^, L - Sent);
SetLength(P.Value, L - Sent);
end
else
begin
FOutBuffer.RemoveFirst.Free;
P := TTCPSendBufferItem(FOutBuffer.First);
end;
end;
end;
procedure ATCPClientSocket.TriggerSendBufferEmpty;
begin
if FClosePending then
SetClosed
else
inherited TriggerSendBufferEmpty;
end;
function ATCPClientSocket.Send(const Buf; const Size: Integer): Integer;
var S : String;
L, Sent, Error : Integer;
P : PChar;
begin
Result := 0;
L := Size;
if L = 0 then
exit;
P := @Buf;
CheckSocketConnected;
// Send as much as possible and buffer the rest
if FReadyToSend and FOutBuffer.IsEmpty then
begin
Sent := SendData(Buf, L, Error);
Result := Sent;
if Sent = L then
exit;
Inc(P, Sent);
Dec(L, Sent);
end;
if not FUseSendBuffer then
exit;
SetLength(S, L);
Move(P^, Pointer(S)^, L);
FOutBuffer.Append(TTCPSendBufferItem.Create(S));
Inc(FOutBufferSize, L);
Result := Size;
end;
function ATCPClientSocket.SendStr(const Buf: String): Integer;
var S : String;
L, Sent, Error : Integer;
P : PChar;
begin
Result := 0;
L := Length(Buf);
if L = 0 then
exit;
CheckSocketConnected;
// Send as much as possible and buffer the rest
if FReadyToSend and FOutBuffer.IsEmpty then
begin
P := Pointer(Buf);
Sent := SendData(P^, L, Error);
Result := L;
if Sent = L then
exit;
Inc(P, Sent);
Dec(L, Sent);
SetLength(S, L);
Move(P^, Pointer(S)^, L);
FOutBuffer.Append(TTCPSendBufferItem.Create(S));
Inc(FOutBufferSize, L);
exit;
end;
if not FUseSendBuffer then
exit;
FOutBuffer.Append(TTCPSendBufferItem.Create(Buf));
Inc(FOutBufferSize, L);
Result := L;
end;
end.