git-svn-id: https://192.168.0.254/svn/Componentes.Terceros.RemObjects@46 b6239004-a887-0f4b-9937-50029ccdca16
957 lines
26 KiB
ObjectPascal
957 lines
26 KiB
ObjectPascal
unit uROAsyncSCHelpers;
|
|
{----------------------------------------------------------------------------}
|
|
{ RemObjects SDK Library - Core Library }
|
|
{ }
|
|
{ compiler: Delphi 5 and up, Kylix 2 and up }
|
|
{ platform: Win32, Linux }
|
|
{ }
|
|
{ (c)opyright RemObjects Software. all rights reserved. }
|
|
{ }
|
|
{ Using this code requires a valid license of the RemObjects SDK }
|
|
{ which can be obtained at http://www.remobjects.com. }
|
|
{----------------------------------------------------------------------------}
|
|
|
|
{$I RemObjects.inc}
|
|
interface
|
|
|
|
uses
|
|
SysUtils, Classes, uROClasses, uROClient, uROClientIntf,
|
|
uROServer {$IFDEF MSWINDOWS}, Windows {$ENDIF}, SyncObjs;
|
|
|
|
type
|
|
EROTimeout = class(EROException);
|
|
TROAckState = (rNone, rAck, rNoAck);
|
|
IROPackageAck = interface
|
|
['{89ABD257-42CB-4B4B-9BA6-3E5CED10605B}']
|
|
function GetEvent: TROEvent;
|
|
function GetAckNo: Integer;
|
|
function GetNoAckError: Integer;
|
|
procedure SetNoAckError(I: Integer);
|
|
function GetAckState: TROAckState;
|
|
procedure SetAckState(I: TROAckState);
|
|
|
|
property Event: TROEvent read GetEvent;
|
|
property AckNo: Integer read GetAckNo;
|
|
property NoAckError: Integer read GetNoAckError write SetNoAckError;
|
|
property AckState: TROAckState read GetAckState write SetAckState;
|
|
|
|
procedure RemoveFromList;
|
|
end;
|
|
IROAbstractTCPConnection = interface;
|
|
TROAsyncCallback = procedure (const Sender: IROAbstractTCPConnection) of object;
|
|
IROAbstractTCPConnection = interface
|
|
['{17BDEB8C-4C92-40FB-9727-DC9F2FA26E8C}']
|
|
procedure BeginReceive(aData: Pointer; aSize: Integer; aCallback: TROAsyncCallback);
|
|
procedure BeginSend(aData: Pointer; aSize: Integer; aCallback: TROAsyncCallback);
|
|
|
|
function EndReceive: Integer;
|
|
function EndSend: Integer;
|
|
|
|
procedure BeginDisconnect(aForce: Boolean; aCallback: TROAsyncCallback);
|
|
procedure EndDisconnect;
|
|
|
|
procedure BeginConnect(const aAdress: string; aPort: Integer; aCallback: TROAsyncCallback);
|
|
function EndConnect: Boolean;
|
|
|
|
procedure SetOnDisconnected(aCallback: TROAsyncCallback);
|
|
function GetOnDisconnected: TROAsyncCallback;
|
|
|
|
property OnDisconnected: TROAsyncCallback read GetOnDisconnected write SetOnDisconnected;
|
|
|
|
procedure SetOnHaveIncompleteData(aCallback: TROAsyncCallback);
|
|
function GetOnHaveIncompleteData: TROAsyncCallback;
|
|
|
|
property OnHaveIncompleteData: TROAsyncCallback read GetOnHaveIncompleteData write SetOnHaveIncompleteData;
|
|
end;
|
|
TByteArray = array of Byte;
|
|
TROAsyncSuperChannelWorker = class(TObject, IUnknown, IROTransport)
|
|
private
|
|
fRefCount: Integer;
|
|
fMaxPackageSize: Integer;
|
|
fIsServer: Boolean;
|
|
fConnection: IROAbstractTCPConnection;
|
|
fClientID: TGUID;
|
|
fLastId: Integer;
|
|
fRunning, fConnected: Boolean;
|
|
fLastData: TDateTime;
|
|
fStateData: Integer;
|
|
|
|
fOtherSideMaxPackageLen: Integer;
|
|
fWaitingAckList: TInterfaceList;
|
|
fRemoteSupportsOptions, fRemoteSkipAck, fSkipAck: Boolean;
|
|
fPingFrequency,
|
|
fPingTimeout: Integer;
|
|
fWriteQueue: TThreadList;
|
|
fServerExpectAck: Integer;
|
|
fWorkBuffer: TByteArray;
|
|
procedure SetSkipAck(const Value: Boolean);
|
|
procedure AsyncWrite(aData: TByteArray);
|
|
procedure AsyncDisconnected(const Sender: IROAbstractTCPConnection);
|
|
procedure AsyncHaveIncompleteData(const aSender: IROAbstractTCPConnection);
|
|
procedure cbAsyncWrite(const Sender: IROAbstractTCPConnection);
|
|
procedure cbServerWelcome(const Sender: IROAbstractTCPConnection);
|
|
procedure cbClientWelcome(const Sender: IROAbstractTCPConnection);
|
|
procedure cbServerWelcomeDone(const Sender: IROAbstractTCPConnection);
|
|
procedure cbClientWelcomeDone(const Sender: IROAbstractTCPConnection);
|
|
procedure cbErrorDone(const Sender: IROAbstractTCPConnection);
|
|
procedure cbMainRead(const Sender: IROAbstractTCPConnection);
|
|
procedure cbTimer(Sender: TObject);
|
|
procedure cbNoAckReply(const Sender: IROAbstractTCPConnection);
|
|
procedure cbOptionsReply(const Sender: IROAbstractTCPConnection);
|
|
procedure SetupMainLoop;
|
|
procedure cbPackageLength(const Sender: IROAbstractTCPConnection);
|
|
procedure cbPackage(const Sender: IROAbstractTCPConnection);
|
|
class procedure cbInternalTimer(CurrentTickCount : cardinal);
|
|
protected
|
|
{ IUnkown methods }
|
|
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
|
|
function _AddRef: Integer; stdcall;
|
|
function _Release: Integer; stdcall;
|
|
|
|
{ IROTransport }
|
|
function GetTransportObject : TObject;
|
|
|
|
property PingFrequency: Integer read fPingFrequency write fPingFrequency;
|
|
property PingTimeout: Integer read fPingTimeout write fPingTimeout;
|
|
property LastData: TDateTime read FLastData;
|
|
property IsServer: Boolean read fIsServer write fIsServer;
|
|
procedure Connected; virtual;
|
|
procedure HaveGuid; virtual;
|
|
procedure SupportsOptions;
|
|
procedure ProcessOption(Data: TByteArray);
|
|
procedure Disconnected(var RestartLoop: Boolean); virtual;
|
|
procedure IncomingData(Id: Integer; aData: TByteArray); virtual; abstract;
|
|
function IntSendData(Id: Integer; aData: TStream): IROPackageAck;
|
|
procedure SetAckDetails(Id: Integer; Oke: Boolean; ErrorNo: Integer); virtual;
|
|
procedure SendOptions(aData: UTF8String);
|
|
function GetDefaultResponse: string; virtual;
|
|
public
|
|
constructor Create(aConnection: IROAbstractTCPConnection);
|
|
destructor Destroy; override;
|
|
|
|
property ClientID: TGuid read fClientID write fClientID;
|
|
property Connection: IROAbstractTCPConnection read fConnection;
|
|
|
|
procedure DoSetup;
|
|
|
|
procedure Disconnect;
|
|
|
|
// Thread safe method
|
|
procedure SendError(Id: Integer; Error: Byte);
|
|
function SendPackage(aData: TStream; Id: Integer = 0): IROPackageAck;
|
|
class procedure WaitForAck(pkg: IROPackageAck; Timeout: Integer = 60000);
|
|
function GenerateId: Integer;
|
|
|
|
// properties
|
|
property MaxPackageSize: Integer read fMaxPackageSize write fMaxPackageSize;
|
|
property IsConnected: Boolean read fConnected write fConnected;
|
|
property SkipAck: Boolean read fSkipAck write SetSkipAck;
|
|
end;
|
|
|
|
const
|
|
ScCmdNoAck_MsgTooLarge = 0;
|
|
ScCmdNoAck_Timeout = 1;
|
|
scCmdNoAck_UnknownCommand = 2;
|
|
ScCmdNoAck_QueueFull = 3;
|
|
ScCmdNoAck_SupportsOptions = 255;
|
|
|
|
|
|
implementation
|
|
|
|
const
|
|
ScWelcome :AnsiString = 'ROSC10';
|
|
|
|
ScCmdHello = 0;
|
|
{
|
|
Client:
|
|
'ROSC10'
|
|
[MyGuid] or [NullGuid]
|
|
MaxPackageLength: Int32;
|
|
Server:
|
|
'ROSC10'
|
|
[YourGuid] or [NewGuid]
|
|
MaxPackageLength: Int32;
|
|
}
|
|
ScCmdPackage = 1;
|
|
{
|
|
Request or Event Id: Int32; (Event < 0; Request/Response > 0)
|
|
Length: Int32;
|
|
Data: [...]
|
|
}
|
|
ScCmdAck = 2;
|
|
{
|
|
Id: Integer;
|
|
}
|
|
ScCmdNoAck = 3;
|
|
{
|
|
Id: Integer;
|
|
Error:
|
|
0 = Message too large
|
|
1 = Timeout
|
|
}
|
|
|
|
ScCmdPing = 4;
|
|
{ Should be sent every 60 seconds by the client
|
|
RandomNumber: Integer;
|
|
}
|
|
ScCmdPong = 5;
|
|
{ Reply to ping
|
|
OriginalRandomNumber: Integer;
|
|
}
|
|
ScCmdOptions = 6;
|
|
{
|
|
Length: Longint
|
|
Data: array of 0..Length -1 of byte
|
|
UTF8 encoded, seperated by #13
|
|
}
|
|
|
|
|
|
|
|
type
|
|
TROPackageAck = class(TInterfacedObject, IROPackageAck)
|
|
private
|
|
fEvent: TROEvent;
|
|
fAckNo,
|
|
FAckNoError: Integer;
|
|
FAckState: TROAckState;
|
|
FOwner: TROAsyncSuperChannelWorker;
|
|
public
|
|
function GetEvent: TROEvent;
|
|
function GetAckNo: Integer;
|
|
function GetNoAckError: Integer;
|
|
procedure SetNoAckError(I: Integer);
|
|
function GetAckState: TROAckState;
|
|
procedure SetAckState(I: TROAckState);
|
|
|
|
constructor Create(aOwner: TROAsyncSuperChannelWorker; aId: Integer);
|
|
destructor Destroy; override;
|
|
|
|
procedure RemoveFromList;
|
|
end;
|
|
|
|
|
|
|
|
var
|
|
fTimer: TROThreadTimer;
|
|
fTimerList: TThreadList;
|
|
|
|
class procedure TROAsyncSuperChannelWorker.cbInternalTimer(CurrentTickCount: cardinal);
|
|
var
|
|
lList: TList;
|
|
i: Integer;
|
|
begin
|
|
lList := fTimerList.LockList;
|
|
try
|
|
for i := lList.Count -1 downto 0 do
|
|
TROAsyncSuperChannelWorker(lList[i]).cbTimer(nil);
|
|
finally
|
|
fTimerList.UnlockList;
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
procedure AddTimer(aCb: TROAsyncSuperChannelWorker);
|
|
var
|
|
i: Integer;
|
|
lList: TList;
|
|
begin
|
|
lList := fTimerList.LockList;
|
|
try
|
|
fTimer := TROThreadTimer.Create(TROAsyncSuperChannelWorker.cbInternalTimer, 30000, false);
|
|
i := lList.IndexOf(aCb);
|
|
if i <> -1 then exit;
|
|
i := lList.IndexOf(nil);
|
|
if i <> -1 then
|
|
lList[i] := aCb
|
|
else
|
|
lList.Add(aCb);
|
|
finally
|
|
fTimerList.UnlockList;
|
|
end;
|
|
end;
|
|
|
|
procedure RemoveTimer(aCb: TROAsyncSuperChannelWorker);
|
|
var
|
|
i: Integer;
|
|
lList: TList;
|
|
begin
|
|
lList := fTimerList.LockList;
|
|
try
|
|
i := lList.IndexOf(aCb);
|
|
if i <> -1 then lList[i] := nil;
|
|
finally
|
|
fTimerList.UnlockList;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TROPackageAck }
|
|
|
|
constructor TROPackageAck.Create(aOwner: TROAsyncSuperChannelWorker; aId: Integer);
|
|
begin
|
|
inherited Create;
|
|
if not (aOwner.fSkipAck and aOwner.fRemoteSupportsOptions) then
|
|
fEvent := TROEvent.Create(nil, true, false, '');
|
|
fAckNo := aId;
|
|
FOwner := aOwner;
|
|
// if FOwner <> nil then FOwner.fWaitingAckList.Add(Self as IROPackageAck);
|
|
end;
|
|
|
|
destructor TROPackageAck.Destroy;
|
|
begin
|
|
fEvent.Free;
|
|
inherited;
|
|
end;
|
|
|
|
function TROPackageAck.GetAckNo: Integer;
|
|
begin
|
|
result := fAckNo;
|
|
end;
|
|
|
|
function TROPackageAck.GetAckState: TROAckState;
|
|
begin
|
|
Result := FAckState;
|
|
end;
|
|
|
|
function TROPackageAck.GetEvent: TROEvent;
|
|
begin
|
|
Result := fEvent;
|
|
end;
|
|
|
|
function TROPackageAck.GetNoAckError: Integer;
|
|
begin
|
|
Result := FAckNoError;
|
|
end;
|
|
|
|
{$ifndef fpc}
|
|
function InterlockedExchangePointer(var Target: Pointer; Value: Pointer): Pointer;
|
|
begin
|
|
Result := Pointer(InterlockedExchange(Integer(Target), Integer(Value)));
|
|
end;
|
|
{$else}
|
|
function InterlockedExchangePointer(var Target: Pointer; Value: Pointer): Pointer;
|
|
begin
|
|
{$IFDEF CPU64}
|
|
result := Pointer(InterlockedExchange64(PtrInt(target), PtrInt(value)));
|
|
{$ELSE}
|
|
result := Pointer(InterlockedExchange(PtrInt(target), PtrInt(value)));
|
|
{$ENDIF}
|
|
end;
|
|
{$endif}
|
|
|
|
procedure TROPackageAck.RemoveFromList;
|
|
var
|
|
lOwner: TROAsyncSuperChannelWorker;
|
|
begin
|
|
lOwner := InterlockedExchangePointer(Pointer(FOwner), nil);
|
|
if lOwner <> nil then lOwner.fWaitingAckList.Remove(self as IROPackageAck);
|
|
end;
|
|
|
|
procedure TROPackageAck.SetAckState(I: TROAckState);
|
|
begin
|
|
FAckState := I;
|
|
end;
|
|
|
|
procedure TROPackageAck.SetNoAckError(I: Integer);
|
|
begin
|
|
FAckNoError := i;
|
|
end;
|
|
|
|
type
|
|
TWriteItem = class
|
|
public
|
|
Data: TByteArray;
|
|
end;
|
|
|
|
{ TROAsyncSuperChannelWorker }
|
|
|
|
procedure TROAsyncSuperChannelWorker.AsyncWrite(aData: TByteArray);
|
|
var
|
|
lList: TList;
|
|
p: TWriteItem;
|
|
begin
|
|
lList := fWriteQueue.LockList;
|
|
try
|
|
p := TwriteItem.Create;
|
|
p.Data := aData;
|
|
lList.Add(p);
|
|
if lList.Count <= 1 then begin
|
|
fConnection.BeginSend(@p.Data[0], Length(p.Data), cbAsyncWrite);
|
|
end;
|
|
finally
|
|
fWriteQueue.UnlockList;
|
|
end;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.cbAsyncWrite(
|
|
const Sender: IROAbstractTCPConnection);
|
|
var
|
|
lList: TList;
|
|
p: TWriteItem;
|
|
begin
|
|
fConnection.EndSend;
|
|
lList := fWriteQueue.LockList;
|
|
try
|
|
if lList.Count = 0 then exit;
|
|
p := lList[0];
|
|
lList.Delete(0);
|
|
p.Free;
|
|
if lList.Count > 0 then begin
|
|
p := lList[0];
|
|
fConnection.BeginSend(@p.Data[0], Length(p.Data), cbAsyncWrite);
|
|
end;
|
|
finally
|
|
fWriteQueue.UnlockList;
|
|
end;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.Connected;
|
|
begin
|
|
// do nothing
|
|
end;
|
|
|
|
constructor TROAsyncSuperChannelWorker.Create(
|
|
aConnection: IROAbstractTCPConnection);
|
|
begin
|
|
inherited Create;
|
|
fPingFrequency := 60;
|
|
fPingTimeout := 90;
|
|
fConnection := aConnection;
|
|
fMaxPackageSize := 10*1024 * 1024; // 10mb
|
|
fWaitingAckList:= TInterfaceList.Create;
|
|
fWriteQueue := TThreadList.Create;
|
|
end;
|
|
|
|
destructor TROAsyncSuperChannelWorker.Destroy;
|
|
var
|
|
i: Integer;
|
|
pb: TWriteItem;
|
|
lItems: TList;
|
|
begin
|
|
RemoveTimer(self);
|
|
fWaitingAckList.Lock;
|
|
try
|
|
for i := fWaitingAckList.Count -1 downto 0 do
|
|
IROPackageAck(fWaitingAckList[i]).RemoveFromList;
|
|
finally
|
|
fWaitingAckList.Unlock;
|
|
end;
|
|
lItems := fWriteQueue.LockList;
|
|
try
|
|
while lItems.Count > 0 do begin
|
|
pb := lItems[0];
|
|
lItems.Delete(0);
|
|
pb.Free;
|
|
end;
|
|
finally
|
|
fWriteQueue.UnlockList;
|
|
end;
|
|
fWriteQueue.Free;
|
|
fWaitingAckList.Free;
|
|
fConnection.OnDisconnected := nil;
|
|
fConnection.OnHaveIncompleteData := nil;
|
|
fConnection := nil;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.Disconnected(
|
|
var RestartLoop: Boolean);
|
|
begin
|
|
RestartLoop := false;
|
|
// Do nothing
|
|
end;
|
|
|
|
function TROAsyncSuperChannelWorker.GenerateId: Integer;
|
|
begin
|
|
fWriteQueue.LockList;
|
|
try
|
|
if IsServer then
|
|
begin
|
|
fLastId := fLastId - 1;
|
|
if fLastId > -1 then fLastId := -1;
|
|
end
|
|
else
|
|
begin
|
|
fLastId := fLastId + 1;
|
|
if fLastId < 1 then fLastId := 1;
|
|
end;
|
|
Result := fLastId;
|
|
finally
|
|
fWriteQueue.UnlockList;
|
|
end;
|
|
end;
|
|
|
|
function TROAsyncSuperChannelWorker.GetTransportObject: TObject;
|
|
begin
|
|
Result := Self;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.SupportsOptions;
|
|
begin
|
|
if fSkipAck then SendOptions('SKIPACK'#13'ON');
|
|
fRemoteSupportsOptions := true;
|
|
end;
|
|
|
|
function TROAsyncSuperChannelWorker.IntSendData(Id: Integer;
|
|
aData: TStream): IROPackageAck;
|
|
var
|
|
Buffer: TByteArray;
|
|
len: Integer;
|
|
begin
|
|
fLastData := Now;
|
|
Result := TROPackageAck.Create(Self, Id);
|
|
fWaitingAckList.Add(Result);
|
|
SetLength(Buffer, (4 + 4 + 1) + aData.Size);
|
|
Buffer[0] := ScCmdPackage;
|
|
Move(Id, Buffer[1], 4);
|
|
Len := aData.Size;
|
|
Move(Len, Buffer[5], 4);
|
|
|
|
aData.Seek(0, soFromBeginning);
|
|
aData.Read(Buffer[9], aData.Size);
|
|
AsyncWrite(Buffer);
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.ProcessOption(Data: TByteArray);
|
|
var
|
|
lCmd, lData: UTF8String;
|
|
lPos: Integer;
|
|
begin
|
|
SetLength(lData, Length(Data));
|
|
Move(Data[0], lData[1], Length(lData));
|
|
lPos := Pos(AnsiChar(#13), lData);
|
|
if lPos = 0 then begin
|
|
lCmd := lData;
|
|
lData := '';
|
|
end else begin
|
|
lCmd := copy(lData, 1, lPos-1);
|
|
Delete(lData, 1, lPos);
|
|
end;
|
|
if lCmd = 'SKIPACK' then begin
|
|
fRemoteSkipAck := lData <> 'OFF';
|
|
end;
|
|
end;
|
|
|
|
|
|
function TROAsyncSuperChannelWorker.QueryInterface(const IID: TGUID;
|
|
out Obj): HResult;
|
|
begin
|
|
if GetInterface(IID, Obj) then
|
|
Result := 0
|
|
else
|
|
Result := E_NOINTERFACE;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.SendError(Id: Integer; Error: Byte);
|
|
var
|
|
Buffer: TByteArray;
|
|
begin
|
|
SetLength(Buffer, 6);
|
|
buffer[0] := ScCmdNoAck;
|
|
Move(ID, Buffer[1], 4);
|
|
Buffer[5] := Error;
|
|
AsyncWrite(Buffer);
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.SendOptions(aData: UTF8String);
|
|
var
|
|
Buffer: TByteArray;
|
|
Len: Integer;
|
|
begin
|
|
fLastData := Now;
|
|
SetLength(Buffer, Length(aData)+5);
|
|
Buffer[0] := ScCmdOptions;
|
|
Len := Length(aData);
|
|
Move(Len, Buffer[1], 4);
|
|
Move(aData[1], Buffer[5], Length(aDAta));
|
|
AsyncWrite(Buffer);
|
|
end;
|
|
|
|
function TROAsyncSuperChannelWorker.SendPackage(aData: TStream;
|
|
Id: Integer): IROPackageAck;
|
|
begin
|
|
if fOtherSideMaxPackageLen < aData.Size then begin
|
|
raise EROException.Create('Package too large');
|
|
end
|
|
else begin
|
|
if Id = 0 then
|
|
begin
|
|
id := GenerateId;
|
|
end;
|
|
Result := IntSendData(Id, aData);
|
|
end;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.SetAckDetails(Id: Integer;
|
|
Oke: Boolean; ErrorNo: Integer);
|
|
var
|
|
i: Integer;
|
|
rp: IROPackageAck;
|
|
begin
|
|
fWaitingAckList.Lock;
|
|
try
|
|
for i := fWaitingAckList.Count -1 downto 0 do
|
|
begin
|
|
if IROPackageAck(fWaitingAckList[i]).AckNo = Id then
|
|
begin
|
|
rp := IROPackageAck(fWaitingAckList[i]);
|
|
rp.RemoveFromList;
|
|
|
|
rp.NoAckError := ErrorNo;
|
|
if Oke then
|
|
rp.AckState := rAck
|
|
else
|
|
rp.AckState := rNoAck;
|
|
rp.Event.SetEvent;
|
|
exit;
|
|
end;
|
|
end;
|
|
finally
|
|
fWaitingAckList.Unlock;
|
|
end;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.SetSkipAck(const Value: Boolean);
|
|
begin
|
|
fSkipAck := Value;
|
|
end;
|
|
|
|
class procedure TROAsyncSuperChannelWorker.WaitForAck(pkg: IROPackageAck;
|
|
Timeout: Integer);
|
|
begin
|
|
try
|
|
if pkg.Event = nil then exit; // skipack
|
|
|
|
if pkg.Event.WaitFor(Timeout) <> wrSignaled then
|
|
raise EROTimeout.Create('Timeout');
|
|
if pkg.AckState <> rAck then
|
|
begin
|
|
case pkg.NoAckError of
|
|
ScCmdNoAck_MsgTooLarge: raise eROMessageTooLarge.Create('Message too large');
|
|
else
|
|
raise EROException.Create('Unknown error');
|
|
end;
|
|
end;
|
|
finally
|
|
pkg.RemoveFromList;
|
|
end;
|
|
end;
|
|
|
|
function TROAsyncSuperChannelWorker._AddRef: Integer;
|
|
begin
|
|
Result := InterlockedIncrement(fRefCount);
|
|
end;
|
|
|
|
function TROAsyncSuperChannelWorker._Release: Integer;
|
|
begin
|
|
Result := InterlockedDecrement(fRefCount);
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.DoSetup;
|
|
var
|
|
lWrite: TList;
|
|
i: Integer;
|
|
lData: TByteArray;
|
|
begin
|
|
fRunning := true;
|
|
fRemoteSupportsOptions := false;
|
|
fRemoteSkipAck := false;
|
|
fServerExpectAck := 0;
|
|
lWrite := fWriteQueue.LockList;
|
|
try
|
|
for i:= 0 to lWRite.Count -1 do begin
|
|
Pointer(lData) := lWrite[i];
|
|
lData := nil;
|
|
end;
|
|
lWrite.Clear;
|
|
finally
|
|
fWriteQueue.UnlockList;
|
|
end;
|
|
fLastData := now;
|
|
fConnection.OnDisconnected := AsyncDisconnected;
|
|
fConnection.OnHaveIncompleteData := AsyncHaveIncompleteData;
|
|
|
|
if fIsServer then begin
|
|
SetLength(lData, 26);
|
|
fWorkBuffer := lData;
|
|
fConnection.BeginReceive(@lData[0], 26, cbServerWelcome);
|
|
end else begin
|
|
SetLength(lData, 26);
|
|
fWorkBuffer := lData;
|
|
Move(ScWelcome[1], lData[0], Length(ScWelcome));
|
|
Move(fClientId, lData[6], 16);
|
|
Move(fMaxPackageSize, lData[22], 4);
|
|
fConnection.BeginSend(@lData[0], 26, cbClientWelcome);
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
procedure TROAsyncSuperChannelWorker.cbClientWelcome(
|
|
const Sender: IROAbstractTCPConnection);
|
|
begin
|
|
fConnection.EndSend;
|
|
fConnection.BeginReceive(@fWorkBuffer[0], 26, cbClientWelcomeDone);
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.cbServerWelcome(
|
|
const Sender: IROAbstractTCPConnection);
|
|
var
|
|
lData: Ansistring;
|
|
begin
|
|
if fConnection.EndReceive <> 26 then fWorkBuffer := nil;
|
|
|
|
if (fWorkBuffer = nil) or (fWorkBuffer[0] <> byte(scWelcome[1]))
|
|
or (fWorkBuffer[1] <> byte(scWelcome[2]))
|
|
or (fWorkBuffer[2] <> byte(scWelcome[3]))
|
|
or (fWorkBuffer[3] <> byte(scWelcome[4]))
|
|
or (fWorkBuffer[4] <> byte(scWelcome[5]))
|
|
or (fWorkBuffer[5] <> byte(scWelcome[6])) then begin
|
|
lData :={$IFDEF UNICODE}WideStringToAnsiString{$ENDIF} (GetDefaultResponse);
|
|
SetLength(fWorkBuffer, Length(lData));
|
|
move(lData[1], fWorkBuffer[0], Length(lData));
|
|
fConnection.BeginSend(@fWorkBuffer[0], Length(fWorkBuffer), cbErrorDone);
|
|
exit;
|
|
end;
|
|
Move(fWorkBuffer[6], fClientID, 16);
|
|
Move(fWorkBuffer[22], fOtherSideMaxPackageLen, 4);
|
|
if IsEqualGUID(fClientID, EmptyGUID) then
|
|
fClientID := NewGuid;
|
|
Move(fClientID, fWorkBuffer[6], 16);
|
|
|
|
Move(fMaxPackageSize, fWorkBuffer[22], 4);
|
|
|
|
fConnection.BeginSend(@fWorkBuffer[0], 26, cbServerWelcomeDone);
|
|
HaveGuid;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.cbClientWelcomeDone(
|
|
const Sender: IROAbstractTCPConnection);
|
|
var
|
|
lData: Ansistring;
|
|
begin
|
|
if fConnection.EndReceive <> 26 then fWorkBuffer := nil;
|
|
if (fWorkBuffer = nil) or (fWorkBuffer[0] <> Byte(scWelcome[1]))
|
|
or (fWorkBuffer[1] <> Byte(scWelcome[2]))
|
|
or (fWorkBuffer[2] <> Byte(scWelcome[3]))
|
|
or (fWorkBuffer[3] <> Byte(scWelcome[4]))
|
|
or (fWorkBuffer[4] <> Byte(scWelcome[5]))
|
|
or (fWorkBuffer[5] <> Byte(scWelcome[6])) then begin
|
|
lData := {$IFDEF UNICODE}WideStringToAnsiString{$ENDIF}(GetDefaultResponse);
|
|
SetLength(fWorkBuffer, Length(lData));
|
|
move(lData[1], fWorkBuffer[0], Length(lData));
|
|
fConnection.BeginSend(@fWorkBuffer[0], Length(fWorkBuffer), cbErrorDone);
|
|
exit;
|
|
end;
|
|
|
|
Move(fWorkBuffer[6], fClientID, 16);
|
|
Move(fWorkBuffer[22], fOtherSideMaxPackageLen, 4);
|
|
HaveGuid;
|
|
|
|
SetupMainLoop;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.cbServerWelcomeDone(
|
|
const Sender: IROAbstractTCPConnection);
|
|
begin
|
|
fConnection.EndSend;
|
|
SetupMainLoop;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.cbErrorDone(
|
|
const Sender: IROAbstractTCPConnection);
|
|
begin
|
|
fConnection.EndSend;
|
|
fConnection.BeginDisconnect(True, nil);
|
|
AsyncDisconnected(Sender);
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.AsyncHaveIncompleteData(
|
|
const aSender: IROAbstractTCPConnection);
|
|
begin
|
|
fLastData := Now;
|
|
end;
|
|
|
|
|
|
procedure TROAsyncSuperChannelWorker.HaveGuid;
|
|
begin
|
|
Connected;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.SetupMainLoop;
|
|
begin
|
|
SetLength(fWorkBuffer, 6);
|
|
fWorkbuffer[0] := ScCmdNoAck;
|
|
fWorkBuffer[1] := 0;
|
|
fWorkBuffer[2] := 0;
|
|
fWorkBuffer[3] := 0;
|
|
fWorkBuffer[4] := 0;
|
|
fWorkBuffer[5] := ScCmdNoAck_SupportsOptions;
|
|
AsyncWrite(fWorkBuffer);
|
|
setlength(fWorkBuffer, 5);
|
|
fConnection.BeginReceive(@fWorkBuffer[0], 5, cbMainRead);
|
|
AddTimer(self);
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.AsyncDisconnected (const Sender: IROAbstractTCPConnection);
|
|
begin
|
|
RemoveTimer(self);
|
|
fRunning := False;
|
|
Disconnected(fRunning);
|
|
if fRunning then DoSetup;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.Disconnect;
|
|
begin
|
|
fConnection.BeginDisconnect(true, nil);
|
|
AsyncDisconnected(fConnection);
|
|
end;
|
|
|
|
|
|
|
|
procedure TROAsyncSuperChannelWorker.cbTimer(Sender: TObject);
|
|
var
|
|
lData: TByteArray;
|
|
begin
|
|
if fIsServer then begin
|
|
if ((Now - fLastData) * (60 * 60 * 24)) > fPingTimeout then begin
|
|
fConnection.BeginDisconnect(true, nil);
|
|
AsyncDisconnected(fConnection);
|
|
end;
|
|
end else begin
|
|
if fServerExpectAck <> 0 then begin
|
|
if ((Now - fLastData) * (60 * 60 * 24)) > fPingTimeout then begin
|
|
fConnection.BeginDisconnect(true, nil);
|
|
AsyncDisconnected(fConnection);
|
|
end;
|
|
end else begin
|
|
fServerExpectAck := Trunc(Now * 24 * 60 * 60 * 7);
|
|
if fServerExpectAck = 0 then fServerExpectAck := 1;
|
|
SetLength(lData, 5);
|
|
lData[0] := ScCmdPing;
|
|
Move(fSErverExpectAck, lData[1], 4);
|
|
AsyncWrite(lData);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.cbNoAckReply(const Sender: IROAbstractTCPConnection);
|
|
begin
|
|
fConnection.EndReceive;
|
|
if fWorkBuffer[0] = ScCmdNoAck_SupportsOptions then
|
|
SupportsOptions
|
|
else
|
|
SetAckDetails(fStateData, false, fWorkBuffer[0]);
|
|
fConnection.BeginReceive(@fWorkBuffer[0], 5, cbMainRead);
|
|
end;
|
|
procedure TROAsyncSuperChannelWorker.cbOptionsReply(const Sender: IROAbstractTCPConnection);
|
|
begin
|
|
fConnection.EndReceive;
|
|
ProcessOption(fWorkBuffer);
|
|
SetLength(fWorkBuffer, 5);
|
|
fConnection.BeginReceive(@fWorkBuffer[0], 5, cbMainRead);
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.cbPackageLength(const Sender: IROAbstractTCPConnection);
|
|
var
|
|
lLen: Integer;
|
|
begin
|
|
fConnection.EndREceive;
|
|
Move(FWorkBuffer[0], lLen, 4);
|
|
if (lLen < 0) or (lLen > fMaxPackageSize) then begin
|
|
fConnection.BeginDisconnect(true, nil);
|
|
AsyncDisconnected(fConnection);
|
|
exit;
|
|
end;
|
|
SetLength(fWorkBuffer, lLen);
|
|
fConnection.BeginReceive(@fWorkBuffer[0], lLen, cbPackage);
|
|
end;
|
|
|
|
procedure TROAsyncSuperChannelWorker.cbPackage(const Sender: IROAbstractTCPConnection);
|
|
var
|
|
lData: TByteArray;
|
|
begin
|
|
fConnection.EndReceive;
|
|
try
|
|
IncomingData(fStateData, fWorkBuffer);
|
|
if not fRemoteSkipAck then begin
|
|
SetLength(lData, 5);
|
|
lData[0] := ScCmdAck;
|
|
Move(fStateData, lData[1], 4);
|
|
AsyncWrite(lData);
|
|
end;
|
|
except
|
|
SendError(fStateData, ScCmdNoAck_QueueFull);
|
|
end;
|
|
SetLength(fWorkBuffer, 5);
|
|
fConnection.BeginReceive(@fWorkBuffer[0], 5, cbMainRead);
|
|
end;
|
|
|
|
|
|
procedure TROAsyncSuperChannelWorker.cbMainRead(const Sender: IROAbstractTCPConnection);
|
|
var
|
|
lTemp: TByteArray;
|
|
begin
|
|
fLastData := Now;
|
|
lTemp := nil;
|
|
fConnection.EndReceive;
|
|
case fWorkBuffer[0] of
|
|
ScCmdPong:
|
|
begin
|
|
if Integer((@fWorkBuffer[1])^) = fServerExpectAck then fServerExpectAck := 0;
|
|
fConnection.BeginReceive(@fWorkBuffer[0], 5, cbMainRead);
|
|
end;
|
|
ScCmdPing:
|
|
begin
|
|
fWorkBuffer[0] := ScCmdPong;
|
|
AsyncWrite(fWorkBuffer);
|
|
fWorkBuffer := nil;
|
|
SetLength(fWorkBuffer, 5);
|
|
fConnection.BeginReceive(@fWorkBuffer[0], 5, cbMainRead);
|
|
end;
|
|
ScCmdAck:
|
|
begin
|
|
SetAckDetails(Integer((@fWorkBuffer[1])^), true, 0);
|
|
fConnection.BeginReceive(@fWorkBuffer[0], 5, cbMainRead);
|
|
end;
|
|
ScCmdNoAck:
|
|
begin
|
|
Move(fWorkBuffer[1], fStateData, 4);
|
|
fConnection.BeginReceive(@fWorkBuffer[0], 1, cbNoAckReply);
|
|
end;
|
|
ScCmdOptions:
|
|
begin
|
|
Move(fWorkBuffer[1], fStateData, 4);
|
|
if fStateData > fMaxPackageSize then begin
|
|
fConnection.BeginDisconnect(true, nil);
|
|
AsyncDisconnected(fConnection);
|
|
exit;
|
|
end;
|
|
SetLength(fWorkBuffer, fStateData);
|
|
fConnection.BeginReceive(@fWorkBuffer[0], fStateData, cbOptionsReply);
|
|
end;
|
|
ScCmdPackage:
|
|
begin
|
|
Move(fWorkBuffer[1], fStateData, 4);
|
|
fConnection.BeginReceive(@fWorkBuffer[0], 4, cbPackageLength);
|
|
end;
|
|
else begin
|
|
fConnection.BeginDisconnect(true, nil);
|
|
AsyncDisconnected(fConnection);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TROAsyncSuperChannelWorker.GetDefaultResponse: string;
|
|
begin
|
|
Result := 'ROSC:Invalid connection string';
|
|
end;
|
|
|
|
initialization
|
|
fTimerList := TThreadList.Create;
|
|
finalization
|
|
fTimer.Free;
|
|
fTimerList.Free;
|
|
end.
|