- Recompilación en Delphi10 de todos los paquetes de RO para generar las DCU's en Lib\D10 git-svn-id: https://192.168.0.254/svn/Componentes.Terceros.RemObjects@7 b6239004-a887-0f4b-9937-50029ccdca16
948 lines
28 KiB
ObjectPascal
948 lines
28 KiB
ObjectPascal
unit uIPAsyncSocket;
|
|
{$R-,Q-}
|
|
interface
|
|
|
|
{$I RemObjects.inc}
|
|
|
|
uses
|
|
SysUtils, Classes, SyncObjs, synsock, uROThread;
|
|
|
|
const
|
|
SocketsPerWorkerThread = 64;
|
|
|
|
type
|
|
EIPSocketError = class(Exception);
|
|
TIPBaseAsyncSocket = class;
|
|
TIPSocketMode = (rsmTCPIP4, rsmTCPIP6);
|
|
TIPSocketCallback = procedure(Sender: TIPBaseAsyncSocket) of object;
|
|
TIPBaseAsyncSocket = class(TObject)
|
|
private
|
|
fBuffered: Boolean;
|
|
fMaxLineLength: Integer;
|
|
|
|
fOnDisconnected: TIPSocketCallback;
|
|
fBufWriteCallback, fBufReadCallback: TIPSocketCallback;
|
|
|
|
fLineSoFar: string;
|
|
fReadLineCallback: TIPSocketCallback;
|
|
fReadLineBuf: array of Byte;
|
|
fReadLineBufStart,
|
|
fReadLineBufEnd: Integer;
|
|
fIdleTimeout: Integer;
|
|
fOnTimeout: TIPSocketCallback;
|
|
|
|
procedure BufferedSendCallback(Sender: TIPBaseAsyncSocket);
|
|
procedure BufferedReceiveCallback(sender: TIPBaseAsyncSocket);
|
|
procedure ReadLineCallback(sender: TIPBaseAsyncSocket);
|
|
|
|
procedure BufferedBeginReceive(aCallback: TIPSocketCallback);
|
|
function BufferedEndReceive: Integer;
|
|
protected
|
|
fLastData: TDateTime;
|
|
|
|
fWriteDestData: Pointer;
|
|
fWriteStart,
|
|
fWriteLength: Integer;
|
|
|
|
fReadDestData: Pointer;
|
|
fReadStart,
|
|
fReadLength: Integer;
|
|
function GetReceiverBufferSize: Integer; virtual; abstract;
|
|
function GetSendBufferSize: Integer; virtual; abstract;
|
|
procedure SetReceiverBufferSize(const Value: Integer); virtual; abstract;
|
|
procedure SetSendBufferSize(const Value: Integer); virtual; abstract;
|
|
|
|
procedure IntBeginSend(aCallback: TIPSocketCallback); virtual; abstract;
|
|
function IntEndSend: Integer; virtual; abstract;
|
|
procedure IntBeginReceive(aCallback: TIPSocketCallback); virtual; abstract;
|
|
function IntEndReceive: Integer; virtual; abstract;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
property IdleTimeout: Integer read fIdleTimeout write fIdleTimeout; // default: 0 = no timeout; Is checked every 30 seconds
|
|
property Buffered: Boolean read fBuffered write fBuffered;
|
|
property MaxLineLength: Integer read fMaxLineLength write fMaxLineLength; // 8192
|
|
function GetLocalPort: Integer; virtual; abstract;
|
|
function GetLocalIp: string; virtual; abstract;
|
|
function GetRemotePort: Integer; virtual; abstract;
|
|
function GetRemoteIp: string; virtual; abstract;
|
|
|
|
property ReceiverBufferSize: Integer read GetReceiverBufferSize write SetReceiverBufferSize;
|
|
property SendBufferSize: Integer read GetSendBufferSize write SetSendBufferSize;
|
|
|
|
procedure BeginAccept(aCallback: TIPSocketCallback); virtual; abstract;
|
|
function EndAccept: TIPBaseAsyncSocket; virtual; abstract;
|
|
|
|
procedure BeginConnect(const aIp: string; aPort: Integer; aCallback: TIPSocketCallback); virtual; abstract;
|
|
function EndConnect: Boolean; virtual; abstract;
|
|
|
|
procedure BeginDisconnect(aCallback: TIPSocketCallback); virtual; abstract;
|
|
procedure EndDisconnect; virtual; abstract;
|
|
|
|
// When Buffered is set; the data to be sent and the data to be received is guaranteed to be of the size requested; or the user has been disconnected
|
|
procedure BeginSend(aData: Pointer; aStart, aLength: Integer; aCallback: TIPSocketCallback);
|
|
function EndSend: Integer;
|
|
|
|
procedure BeginReceive(aData: Pointer; aStart, aLength: Integer; aCallback: TIPSocketCallback);
|
|
function EndReceive: Integer;
|
|
|
|
// returns true if there still is a line in the buffers
|
|
function BufferReadLine(var aLine: string): Boolean;
|
|
|
|
procedure BeginReadLine(aCallback: TIPSocketCallback);
|
|
function EndReadLine(var aLine: string): Boolean;
|
|
|
|
procedure Bind(const aIp: string; aPort: Integer); virtual; abstract;
|
|
procedure Listen(aBacklog: Integer); virtual; abstract;
|
|
|
|
property OnDisconnected: TIPSocketCallback read fOnDisconnected write fOnDisconnected;
|
|
property OnTimeout: TIPSocketCallback read fOnTimeout write fOnTimeout;
|
|
end;
|
|
|
|
TIPAsyncSocket = class;
|
|
|
|
TIPSocketWorkerThread = class(TROThread)
|
|
private
|
|
fReadSet,
|
|
fWriteSet,
|
|
fErrorSet: TFDSet;
|
|
|
|
fCount: Integer;
|
|
fLastTimeoutCheck: TDateTime;
|
|
|
|
fData: array[0.. SocketsPerWorkerThread-1] of TIPAsyncSocket;
|
|
fMyCs: TCriticalSection;
|
|
fSleepEvent: TEvent;
|
|
protected
|
|
procedure Execute; override;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
TIPAsyncSocket = class(TIPBaseAsyncSocket)
|
|
private
|
|
fSock: TSocket;
|
|
fLocSin,
|
|
fRemSin: TVarSin;
|
|
fMode: TIPSocketMode;
|
|
fThread: TIPSocketWorkerThread;
|
|
fReadEvent,
|
|
fWriteEvent,
|
|
fAcceptEvent,
|
|
fConnectEvent,
|
|
fDisconnectEvent: TIPSocketCallback;
|
|
fWantRead,
|
|
fWantWrite,
|
|
fWantError: Boolean;
|
|
fConnectResult: Integer;
|
|
protected
|
|
function GetReceiverBufferSize: Integer; override;
|
|
function GetSendBufferSize: Integer; override;
|
|
procedure IntBeginReceive(aCallback: TIPSocketCallback); override;
|
|
procedure IntBeginSend(aCallback: TIPSocketCallback); override;
|
|
function IntEndReceive: Integer; override;
|
|
function IntEndSend: Integer; override;
|
|
procedure SetReceiverBufferSize(const Value: Integer); override;
|
|
procedure SetSendBufferSize(const Value: Integer); override;
|
|
|
|
constructor Create(aMode: TIPSocketMode; aHandle: TSocket); overload;
|
|
public
|
|
constructor Create(aMode: TIPSocketMode); overload;
|
|
destructor Destroy; override;
|
|
|
|
procedure BeginAccept(aCallback: TIPSocketCallback); override;
|
|
function EndAccept: TIPBaseAsyncSocket; override;
|
|
|
|
procedure BeginConnect(const aIp: String; aPort: Integer; aCallback: TIPSocketCallback); override;
|
|
function EndConnect: Boolean; override;
|
|
|
|
procedure BeginDisconnect(aCallback: TIPSocketCallback); override;
|
|
procedure EndDisconnect; override;
|
|
|
|
procedure Bind(const aIp: String; aPort: Integer); override;
|
|
|
|
function GetLocalIp: String; override;
|
|
function GetLocalPort: Integer; override;
|
|
function GetRemoteIp: String; override;
|
|
function GetRemotePort: Integer; override;
|
|
procedure Listen(aBacklog: Integer); override;
|
|
end;
|
|
|
|
|
|
function InitSocket: Boolean;
|
|
|
|
implementation
|
|
|
|
var
|
|
SockInitialized: Boolean = False;
|
|
WsaData: TWSAData;
|
|
|
|
type
|
|
TByteArray = array[0..4095] of byte;
|
|
PByteArray = ^TByteArray;
|
|
|
|
|
|
var
|
|
WorkerCS: TCriticalSection;
|
|
Workers: TList;
|
|
|
|
function GetWorker(aSock: TIPAsyncSocket): TIPSocketWorkerThread;
|
|
var
|
|
i, j: Integer;
|
|
lWorker: TIPSocketWorkerThread;
|
|
begin
|
|
WorkerCS.Enter;
|
|
try
|
|
for i := 0 to Workers.Count -1 do begin
|
|
lWorker := TIPSocketWorkerThread(Workers[i]);
|
|
if lWorker.fCount < SocketsPerWorkerThread then begin
|
|
lWorker.Fcount := lWorker.fCount +1;
|
|
lWorker.fMyCs.Enter;
|
|
try
|
|
for j := 0 to SocketsPerWorkerThread -1 do begin
|
|
if lworker.fData[j] = nil then begin
|
|
lWorker.FData[j] := aSock;
|
|
result := lWorker;
|
|
exit;
|
|
end;
|
|
end;
|
|
finally
|
|
lWorker.fMyCs.Leave;
|
|
end;
|
|
end;
|
|
end;
|
|
lWorker := TIPSocketWorkerThread.Create;
|
|
lWorker.fMyCs.Enter;
|
|
try
|
|
lWorker.fData[0] :=aSock;
|
|
lWorker.fCount := 1;
|
|
finally
|
|
lWorker.fMyCs.Leave;
|
|
end;
|
|
Workers.Add(lWorker);
|
|
result := lWorker;
|
|
finally
|
|
WorkerCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
procedure ReleaseWorker(aWorker: TIPSocketWorkerThread; fSocket: TIPAsyncSocket);
|
|
var
|
|
j: Integer;
|
|
begin
|
|
if aworker = nil then exit;
|
|
if fSocket = nil then exit;
|
|
aWorker.fMyCs.Enter;
|
|
try
|
|
for j := 0 to SocketsPerWorkerThread -1 do begin
|
|
if aWorker.fData[j] = fSocket then begin
|
|
aWorker.FData[j] := nil;
|
|
aWorker.fCount := aWorker.fCount -1;
|
|
Exit;
|
|
end;
|
|
end;
|
|
finally
|
|
aWorker.fMyCs.Leave;
|
|
end;
|
|
end;
|
|
|
|
function InitSocket: Boolean;
|
|
begin
|
|
if SockInitialized then begin
|
|
result := true;
|
|
exit;
|
|
end;
|
|
|
|
if not InitSocketInterface(DLLStackName) then begin
|
|
result := False;
|
|
exit;
|
|
end;
|
|
synsock.WSAStartup(WinsockLevel, WsaData);
|
|
SockInitialized := true;
|
|
result := true;
|
|
end;
|
|
|
|
|
|
{ TIPBaseAsyncSocket }
|
|
|
|
procedure TIPBaseAsyncSocket.BeginReadLine(aCallback: TIPSocketCallback);
|
|
var
|
|
i, lLen: Integer;
|
|
begin
|
|
if not fBuffered then raise EIPSocketError.Create('Buffered has to be set for BeginReadLine to work');
|
|
if (@fBufReadCallback <> nil) or (@fReadLineCallback <> nil) then raise EIPSocketError.Create('BeginReceive already running');
|
|
fReadLineCallback := aCallback;
|
|
if fReadLineBuf <> nil then begin
|
|
for i := fReadLineBufStart to fReadLineBufEnd -1 do begin
|
|
if fReadLineBuf[i] = 10 then begin
|
|
lLen := i - fReadLineBufStart;
|
|
if (i > fReadLineBufStart) and (fReadLineBuf[i-1] = 13) then dec(lLen);
|
|
SetLength(fLineSoFar, lLen);
|
|
Move(fReadLineBuf[fReadlineBufStart], fLineSoFar[1], lLen);
|
|
fReadLineBufStart := i +1;
|
|
fReadLineCallback := nil;
|
|
aCallback(self);
|
|
exit;
|
|
end;
|
|
end;
|
|
SetLength(fLineSoFar, fReadLineBufEnd - fReadLineBufStart);
|
|
Move(fReadLineBuf[fReadlineBufStart], fLineSoFar[1], fReadLineBufEnd - fReadLineBufStart);
|
|
end;
|
|
if fReadLineBuf = nil then SetLength(fReadLineBuf, 4096);
|
|
fReadLineBufStart := 0;
|
|
fReadLineBufEnd := 0;
|
|
fReadDestData := fReadLineBuf;
|
|
fReadStart := 0;
|
|
fReadLength := 4096;
|
|
IntBeginReceive(ReadLineCallback);
|
|
end;
|
|
|
|
procedure TIPBaseAsyncSocket.BeginReceive(aData: Pointer; aStart, aLength: Integer; aCallback: TIPSocketCallback);
|
|
begin
|
|
if not fBuffered then begin
|
|
fReadDestData := aData;
|
|
fReadStart := aStart;
|
|
fReadLength := aLength;
|
|
IntBeginReceive(aCallback);
|
|
exit;
|
|
end;
|
|
if @aCallback = nil then raise EIPSocketError.Create('Invalid callback');
|
|
if aLength <= 0 then raise EIPSocketError.Create('Invalid length');
|
|
if (@fBufReadCallback <> nil) or (@fReadLineCallback <> nil) then raise EIPSocketError.Create('BeginReceive already running');
|
|
fBufReadCallback := aCallback;
|
|
fReadDestData := @(PByteArray(aData)^[aStart]); // move ahead so we always start at 0;
|
|
fReadStart := 0;
|
|
fReadLength := aLength;
|
|
BufferedBeginReceive(BufferedReceiveCallback);
|
|
end;
|
|
|
|
|
|
procedure TIPBaseAsyncSocket.BufferedBeginReceive(aCallback: TIPSocketCallback);
|
|
begin
|
|
if (fReadLineBuf <> nil) and (fReadLineBufStart < fReadLineBufEnd) then begin
|
|
aCallback(self);
|
|
exit;
|
|
end;
|
|
IntBeginReceive(aCallback);
|
|
end;
|
|
|
|
function TIPBaseAsyncSocket.BufferedEndReceive: Integer;
|
|
begin
|
|
if (fReadLineBufStart < fReadLineBufEnd) and (fReadLineBuf <> nil) then begin
|
|
result := fReadLineBufEnd - fReadLineBufStart;
|
|
if result > fReadLength then result := fReadLength;
|
|
|
|
Move(fReadLineBuf[fReadLineBufStart], PByteArray(fReadDestData)^[fReadStart], Result);
|
|
fReadLineBufStart := fReadLineBufStart + Result;
|
|
fReadStart := fReadStart + Result;
|
|
exit;
|
|
end;
|
|
result := IntEndReceive;
|
|
fReadStart := fReadStart + Result;
|
|
end;
|
|
|
|
|
|
procedure TIPBaseAsyncSocket.BufferedReceiveCallback(
|
|
sender: TIPBaseAsyncSocket);
|
|
var
|
|
lCallback: TIPSocketCallback;
|
|
lEndReceiveResult: Integer;
|
|
begin
|
|
lEndReceiveResult := BufferedEndReceive;
|
|
if lEndReceiveResult <= 0 then begin // disconnected
|
|
// fReadStart := fReadStart + lEndReceiveResult;
|
|
lCallback := fBufReadCallback;
|
|
fBufReadCallback := nil;
|
|
lCallback(self);
|
|
if assigned(fOnDisconnected) then
|
|
fOnDisconnected(Self);
|
|
exit;
|
|
end;
|
|
//ReadStart := fReadStart + lEndReceiveResult;
|
|
if fReadStart < fReadLength then begin
|
|
BufferedBeginReceive(BufferedReceiveCallback);
|
|
end else begin
|
|
lCallback := fBufReadCallback;
|
|
fBufReadCallback := nil;
|
|
lCallback(self);
|
|
end;
|
|
end;
|
|
|
|
function TIPBaseAsyncSocket.EndReceive: Integer;
|
|
begin
|
|
if not fBuffered then begin
|
|
result := IntEndReceive;
|
|
exit;
|
|
end;
|
|
result := fReadStart;
|
|
fWriteDestData := nil;
|
|
fBufWriteCallback := nil;
|
|
end;
|
|
|
|
procedure TIPBaseAsyncSocket.BeginSend(aData: Pointer; aStart,
|
|
aLength: Integer; aCallback: TIPSocketCallback);
|
|
begin
|
|
if not fBuffered then begin
|
|
fWriteDestData:= aData;
|
|
fWriteStart := aStart;
|
|
fWriteLength := aLength;
|
|
IntBeginSend(aCallback);
|
|
exit;
|
|
end;
|
|
if @aCallback = nil then raise EIPSocketError.Create('Invalid callback');
|
|
if aLength <= 0 then raise EIPSocketError.Create('Invalid length');
|
|
if @fBufWriteCallback <> nil then raise EIPSocketError.Create('BeginSend already running');
|
|
|
|
fBufWriteCallback := aCallback;
|
|
fWriteDestData:= @(PByteArray(aData)^[aStart]); // move ahead so we always start at 0
|
|
fWriteStart := 0;
|
|
fWriteLength := aLength;
|
|
IntBeginSend(BufferedSendCallback);
|
|
end;
|
|
|
|
procedure TIPBaseAsyncSocket.BufferedSendCallback(Sender: TIPBaseAsyncSocket);
|
|
var
|
|
lCallback: TIPSocketCallback;
|
|
lEndSendResult: Integer;
|
|
begin
|
|
lEndSendResult := IntEndSend;
|
|
if lEndSendResult <= 0 then begin // disconnected
|
|
fWriteStart := fWriteStart + lEndSendResult;
|
|
lCallback := fBufWriteCallback;
|
|
fBufWriteCallback := nil;
|
|
lCallback(self);
|
|
if assigned(fOnDisconnected) then
|
|
fOnDisconnected(Self);
|
|
exit;
|
|
end;
|
|
fWriteStart := fWriteStart + lEndSendResult;
|
|
if fWriteStart < fWriteLength then begin
|
|
IntBeginSend(BufferedSendCallback);
|
|
end else begin
|
|
lCallback := fBufWriteCallback;
|
|
fBufWriteCallback := nil;
|
|
lCallback(self);
|
|
end;
|
|
end;
|
|
|
|
function TIPBaseAsyncSocket.EndSend: Integer;
|
|
begin
|
|
if not fBuffered then begin
|
|
result := IntEndSend;
|
|
if (Result <= 0) and assigned(fOnDisconnected) then fOnDisconnected(Self);
|
|
exit;
|
|
end;
|
|
result := fWriteStart;
|
|
fWriteDestData := nil;
|
|
fBufWriteCallback := nil;
|
|
end;
|
|
|
|
function TIPBaseAsyncSocket.BufferReadLine(var aLine: string): Boolean;
|
|
var
|
|
lLen, i: Integer;
|
|
begin
|
|
if not fBuffered then raise EIPSocketError.Create('Buffered has to be set for EndReadLine to work');
|
|
if fReadLineBuf <> nil then begin
|
|
for i := fReadLineBufStart to fReadLineBufEnd -1 do begin
|
|
if fReadLineBuf[i] = 10 then begin
|
|
lLen := i - fReadLineBufStart;
|
|
if (i > fReadLineBufStart) and (fReadLineBuf[i-1] = 13) then dec(lLen);
|
|
SetLength(aLine, lLen);
|
|
Move(fReadLineBuf[fReadlineBufStart], aLine[1], lLen);
|
|
fReadLineBufStart := i +1;
|
|
result := true;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
result := false;
|
|
end;
|
|
|
|
constructor TIPBaseAsyncSocket.Create;
|
|
begin
|
|
inherited;
|
|
fMaxLineLength := 8192;
|
|
end;
|
|
|
|
destructor TIPBaseAsyncSocket.Destroy;
|
|
begin
|
|
inherited;
|
|
end;
|
|
|
|
function TIPBaseAsyncSocket.EndReadLine(var aLine: string): Boolean;
|
|
begin
|
|
aLine := fLineSoFar;
|
|
result := fReadLineBufEnd >= 0;
|
|
end;
|
|
|
|
procedure TIPBaseAsyncSocket.ReadLineCallback(sender: TIPBaseAsyncSocket);
|
|
var
|
|
s: string;
|
|
lCb: TIPSocketCallback;
|
|
i, lLen: Integer;
|
|
begin
|
|
fReadLineBufEnd := IntEndReceive;
|
|
if (fReadLineBufEnd <= 0) then begin
|
|
if assigned(fOnDisconnected) then
|
|
OnDisconnected(self);
|
|
exit;
|
|
end;
|
|
|
|
for i := fReadLineBufStart to fReadLineBufEnd -1 do begin
|
|
if fReadLineBuf[i] = 10 then begin
|
|
lLen := i - fReadLineBufStart;
|
|
if (i > fReadLineBufStart) and (fReadLineBuf[i-1] = 13) then dec(lLen);
|
|
SetLength(s, lLen);
|
|
Move(fReadLineBuf[fReadlineBufStart], s[1], lLen);
|
|
fReadLineBufStart := i +1;
|
|
fLineSoFar := fLineSoFar + s;
|
|
lCb := fReadLineCallback;
|
|
fReadLineCallback := nil;
|
|
lCb(self);
|
|
exit;
|
|
end;
|
|
end;
|
|
SetLength(s, fReadLineBufEnd - fReadLineBufStart);
|
|
Move(fReadLineBuf[fReadlineBufStart], s[1], fReadLineBufEnd - fReadLineBufStart);
|
|
fLineSoFar := fLineSoFar + s;
|
|
fReadLineBufStart := 0;
|
|
fReadLineBufEnd := 0;
|
|
fReadDestData := fReadLineBuf;
|
|
fReadStart := 0;
|
|
fReadLength := 4096;
|
|
IntBeginReceive(ReadLineCallback);
|
|
end;
|
|
|
|
{ TIPAsyncSocket }
|
|
|
|
procedure TIPAsyncSocket.BeginAccept(aCallback: TIPSocketCallback);
|
|
begin
|
|
if assigned(fAcceptEvent) then raise EIPSocketError.Create('BeginAccept already called');
|
|
if fThread = nil then fThread := GetWorker(self);
|
|
fAcceptEvent := aCallback;
|
|
fWantRead := true;
|
|
end;
|
|
|
|
procedure TIPAsyncSocket.BeginConnect(const aIp: String; aPort: Integer;
|
|
aCallback: TIPSocketCallback);
|
|
var
|
|
lSin: TVarSin;
|
|
lRes: Integer;
|
|
begin
|
|
if assigned(fConnectEvent) then raise EIPSocketError.Create('BeginConnect already called');
|
|
if fThread = nil then fThread := GetWorker(self);
|
|
|
|
fConnectEvent := aCallback;
|
|
fConnectResult := 0;
|
|
if fMode = rsmTCPIP6 then
|
|
lRes := SetVarSin(lSin, aIp, IntToStr(aPort), AF_INET6, PF_INET6, SOCK_STREAM, false)
|
|
else
|
|
lRes := SetVarSin(lSin, aIp, IntToStr(aPort), AF_INET, PF_INET, SOCK_STREAM, true);
|
|
if lRes <> 0 then raise EIPSocketError.Create('Invalid Address/Port: '+inttostr(WSAGetLastError));
|
|
lRes := Connect(fSock, lSin);
|
|
if lRes <> 0 then begin
|
|
LRes := WSAGetLastError;
|
|
if lRes <> WSAEWOULDBLOCK then raise EIPSocketError.Create('Cannot connect: '+inttostr(Lres));
|
|
end;
|
|
fWantWrite := true;
|
|
fWantError := true;
|
|
end;
|
|
|
|
procedure TIPAsyncSocket.BeginDisconnect(aCallback: TIPSocketCallback);
|
|
begin
|
|
if assigned(fDisconnectEvent) then raise EIPSocketError.Create('BeginDisconnect already called');
|
|
fDisconnectEvent := aCallback;
|
|
Synsock.Shutdown(FSock, 1);
|
|
fWantRead := true;
|
|
end;
|
|
|
|
procedure TIPAsyncSocket.Bind(const aIp: String; aPort: Integer);
|
|
var
|
|
lRes: Integer;
|
|
lSin: TVarSin;
|
|
begin
|
|
if fMode = rsmTCPIP6 then
|
|
lRes := SetVarSin(lSin, aIp, IntToStr(aPort), AF_INET6, PF_INET6, SOCK_STREAM, false)
|
|
else
|
|
lRes := SetVarSin(lSin, aIp, IntToStr(aPort), AF_INET, PF_INET, SOCK_STREAM, true);
|
|
if lRes <> 0 then raise EIPSocketError.Create('Invalid Address/Port: '+inttostr(WSAGetLastError));
|
|
lRes := SynSock.Bind(fSock, lSin);
|
|
if lRes <> 0 then raise EIPSocketError.Create('Cannot bind: '+inttostr(WSAGetLastError));
|
|
end;
|
|
|
|
constructor TIPAsyncSocket.Create(aMode: TIPSocketMode);
|
|
var
|
|
lRes, lVal: Integer;
|
|
begin
|
|
inherited Create;
|
|
if not InitSocket then raise EIPSocketError.Create('Could not load socket stack');
|
|
fMode := aMode;
|
|
if aMode = rsmTCPIP6 then
|
|
fSock := Socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)
|
|
else
|
|
fSock := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if fSock <= 0 then raise EIPSocketError.Create('Unable to create socket');
|
|
lVal := 1;
|
|
lRes := IoctlSocket(fsock, FIONBIO, lVal);
|
|
if lRes <> 0 then raise EIPSocketError.Create('Cannot set socket to asynchronous mode: '+inttostr(synsock.WSAGetLastError));
|
|
end;
|
|
|
|
constructor TIPAsyncSocket.Create(aMode: TIPSocketMode; aHandle: TSocket);
|
|
var
|
|
lVal, lRes: Integer;
|
|
|
|
begin
|
|
inherited Create;
|
|
fMode := aMode;
|
|
fSock := aHandle;
|
|
lVal := 1;
|
|
lRes := IoctlSocket(fsock, FIONBIO, lVal);
|
|
if lRes <> 0 then raise EIPSocketError.Create('Cannot set socket to asynchronous mode');
|
|
if fThread = nil then fThread := GetWorker(self);
|
|
end;
|
|
|
|
destructor TIPAsyncSocket.Destroy;
|
|
begin
|
|
if fThread <> nil then begin
|
|
ReleaseWorker(fThread, self);
|
|
fThread := nil;
|
|
end;
|
|
if FSock <>0 then
|
|
CloseSocket(fSock);
|
|
inherited;
|
|
end;
|
|
|
|
function TIPAsyncSocket.EndAccept: TIPBaseAsyncSocket;
|
|
var
|
|
lAddr: TVarSin;
|
|
lSock: TSocket;
|
|
begin
|
|
fAcceptEvent := nil;
|
|
|
|
lSock := synsock.Accept(fSock, lAddr);
|
|
|
|
if lSock <= 0 then raise EIPSocketError.Create('Cannot accept connection');
|
|
Result := TIPAsyncSocket.Create(fMode, lSock);
|
|
fWantRead := false;
|
|
end;
|
|
|
|
function TIPAsyncSocket.EndConnect: Boolean;
|
|
begin
|
|
if fConnectResult = 0 then result := true
|
|
else result := false;
|
|
fConnectEvent := nil;
|
|
fWantWrite := false;
|
|
fWantError := false;
|
|
end;
|
|
|
|
procedure TIPAsyncSocket.EndDisconnect;
|
|
begin
|
|
fDisconnectEvent := nil;
|
|
CloseSocket(fSock);
|
|
if fThread <> nil then begin
|
|
ReleaseWorker(fThread, self);
|
|
fThread := nil;
|
|
end;
|
|
fSock := 0;
|
|
fWantRead := false;
|
|
end;
|
|
|
|
function TIPAsyncSocket.GetLocalIp: String;
|
|
begin
|
|
if fLocSin.AddressFamily = 0 then
|
|
synsock.GetSockName(FSock, fLocSin);
|
|
result := GetSinIP(fLocSin);
|
|
end;
|
|
|
|
function TIPAsyncSocket.GetLocalPort: Integer;
|
|
begin
|
|
if fLocSin.AddressFamily = 0 then
|
|
synsock.GetSockName(FSock, fLocSin);
|
|
result := GetSinPort(fLocSin);
|
|
end;
|
|
|
|
function TIPAsyncSocket.GetReceiverBufferSize: Integer;
|
|
var
|
|
lRes: Integer;
|
|
begin
|
|
result := 0;
|
|
lRes := 4;
|
|
GetSockOpt(fSock, SOL_Socket, SO_RCVBUF, PChar(@Result), lRes);
|
|
end;
|
|
|
|
function TIPAsyncSocket.GetRemoteIp: String;
|
|
begin
|
|
if fRemSin.AddressFamily = 0 then
|
|
synsock.GetPeerName(FSock, fRemSin);
|
|
result := GetSinIp(fRemSin);
|
|
end;
|
|
|
|
function TIPAsyncSocket.GetRemotePort: Integer;
|
|
begin
|
|
if fRemSin.AddressFamily = 0 then
|
|
synsock.GetPeerName(FSock, fRemSin);
|
|
result := GetSinPort(fRemSin);
|
|
end;
|
|
|
|
function TIPAsyncSocket.GetSendBufferSize: Integer;
|
|
var
|
|
lRes: Integer;
|
|
begin
|
|
result := 0;
|
|
lRes := 4;
|
|
GetSockOpt(fSock, SOL_Socket, SO_SNDBUF, PChar(@Result), lRes);
|
|
end;
|
|
|
|
procedure TIPAsyncSocket.IntBeginReceive(aCallback: TIPSocketCallback);
|
|
begin
|
|
if assigned(fReadEvent) then raise EIPSocketError.Create('beginReceive already called');
|
|
fLastData := Now;
|
|
fReadEvent := aCallback;
|
|
fWantRead := true;
|
|
end;
|
|
|
|
procedure TIPAsyncSocket.IntBeginSend(aCallback: TIPSocketCallback);
|
|
begin
|
|
if assigned(fWriteEvent) then raise EIPSocketError.Create('BeginSend already called');
|
|
fLastData := Now;
|
|
fWriteEvent := aCallback;
|
|
fWantWrite := true;
|
|
end;
|
|
|
|
function TIPAsyncSocket.IntEndReceive: Integer;
|
|
begin
|
|
fwantRead := false;
|
|
fReadEvent := nil;
|
|
fLastData := Now;
|
|
result := Recv(fSock, @PByteArray(fReadDestData)[fReadStart], fReadLength, 0);
|
|
end;
|
|
|
|
function TIPAsyncSocket.IntEndSend: Integer;
|
|
begin
|
|
fWriteEvent := nil;
|
|
fWantWrite := false;
|
|
fLastData := Now;
|
|
Result := Send(fSock, @PByteArray(fWriteDestData)[fWriteStart], fWriteLength, 0);
|
|
end;
|
|
|
|
procedure TIPAsyncSocket.Listen(aBacklog: Integer);
|
|
begin
|
|
synsock.Listen(fSock, aBacklog);
|
|
end;
|
|
|
|
procedure TIPAsyncSocket.SetReceiverBufferSize(const Value: Integer);
|
|
var
|
|
lRes: Integer;
|
|
begin
|
|
lRes := SetSockOpt(fsock, SOL_Socket, SO_RCVBUF, pchar(@value), 4);
|
|
if lRes <> 0 then raise EIPSocketError.Create('Unable to set option: '+Inttostr(WSAGetLastError));
|
|
end;
|
|
|
|
procedure TIPAsyncSocket.SetSendBufferSize(const Value: Integer);
|
|
var
|
|
lRes: Integer;
|
|
begin
|
|
lRes := SetSockOpt(fsock, SOL_Socket, SO_SNDBUF, pchar(@value), 4);
|
|
if lRes <> 0 then raise EIPSocketError.Create('Unable to set option: '+Inttostr(WSAGetLastError));
|
|
end;
|
|
|
|
procedure CleanupWorkers;
|
|
var
|
|
i: Integer;
|
|
lWorker: TIPSocketWorkerThread;
|
|
begin
|
|
WorkerCS.Enter;
|
|
try
|
|
for i := Workers.Count - 1 downto 0 do begin
|
|
lWorker := TIPSocketWorkerThread(Workers[i]);
|
|
lWorker.Free;
|
|
end;
|
|
finally
|
|
WorkerCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
{ TIPSocketWorkerThread }
|
|
|
|
constructor TIPSocketWorkerThread.Create;
|
|
begin
|
|
inherited Create(True);
|
|
fMyCs := TCriticalSection.Create;
|
|
fSleepEvent := TEvent.Create(nil, false, false, '');
|
|
Resume;
|
|
end;
|
|
|
|
destructor TIPSocketWorkerThread.Destroy;
|
|
begin
|
|
fSleepEvent.Free;
|
|
FMyCs.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
const
|
|
TimeoutCheckSpan = 1.0 / (24 * 60 * 2); // every 30 sec
|
|
OneSecond = 1.0 / (24 * 60 * 60);
|
|
|
|
procedure TIPSocketWorkerThread.Execute;
|
|
var
|
|
i: Integer;
|
|
lCount: Integer;
|
|
lDummy: Integer;
|
|
lDate: TDateTime;
|
|
lSock: TIPAsyncSocket;
|
|
lEvent: TIPSocketCallback;
|
|
TimeVal: TTimeVal;
|
|
begin
|
|
TimeVal.tv_sec := 0;
|
|
TimeVal.tv_usec := 50;
|
|
fLastTimeoutCheck := Now;
|
|
while not Terminated do
|
|
begin
|
|
fMyCs.Enter;
|
|
try
|
|
lDate := Now;
|
|
if (lDate - fLastTimeoutCheck) > TimeoutCheckSpan then begin
|
|
fLastTimeoutCheck := lDate;
|
|
for i := 0 to SocketsPerWorkerThread -1 do begin
|
|
lSock := fData[i];
|
|
if lSock <> nil then begin
|
|
if (lSock.IdleTimeout > 0) and (lSock.fLastData + (OneSecond * lSock.IdleTimeout) < lDate) then begin
|
|
fMyCs.Leave;
|
|
try
|
|
if assigned(lSock.OnTimeout) then lSock.OnTimeout(lSock);
|
|
finally
|
|
fMyCs.Enter;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
fSleepEvent.ResetEvent;
|
|
lCount := fCount ;
|
|
if lCount > 0 then begin
|
|
FD_ZERO(fReadSet);
|
|
FD_ZERO(fWriteSet);
|
|
FD_ZERO(fErrorSet);
|
|
|
|
for i := 0 to SocketsPerWorkerThread -1 do begin
|
|
if fData[i] <> nil then begin
|
|
if fData[i].fWantRead then
|
|
FD_Set(fData[i].fSock, fReadset);
|
|
if fData[i].fWantWrite then
|
|
FD_Set(fData[i].fSock, fWriteset);
|
|
if fData[i].fWantError then
|
|
FD_Set(fData[i].fSock, fErrorSet);
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
fMyCs.Leave;
|
|
end;
|
|
if lCount = 0 then fSleepEvent.WaitFor(1000) else begin
|
|
lCount := Select(lCount, @fReadSet, @FwriteSet, @fErrorSet, @TimeVal);
|
|
if lCount > 0 then begin
|
|
fMycs.Enter;
|
|
try
|
|
for i := 0 to SocketsPerWorkerThread do begin
|
|
if lCount = 0 then break;
|
|
if Fdata[i] = nil then continue;
|
|
|
|
|
|
if FD_ISSET(fData[i].fSock, fReadSet) then begin // Read, Accept and Disconnect
|
|
lEvent := fData[i].fReadEvent;
|
|
if assigned(lEvent) then begin
|
|
fMyCS.Leave;
|
|
try
|
|
dec(lCount);
|
|
lEvent(fData[i]);
|
|
finally
|
|
fMyCs.Enter;
|
|
end;
|
|
end else begin
|
|
lEvent := fData[i].fAcceptEvent;
|
|
if assigned(lEvent) then begin
|
|
fMyCS.Leave;
|
|
try
|
|
dec(lCount);
|
|
lEvent(fData[i]);
|
|
finally
|
|
fMyCs.Enter;
|
|
end;
|
|
end else begin
|
|
lEvent := fData[i].fDisconnectEvent;
|
|
if assigned(lEvent) then begin
|
|
fMyCS.Leave;
|
|
try
|
|
dec(lCount);
|
|
lEvent(fData[i]);
|
|
finally
|
|
fMyCs.Enter;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end else
|
|
|
|
if FD_ISSET(fData[i].FSock, FWriteset) then begin // Connect, Write
|
|
lEvent := fData[i].fWriteEvent;
|
|
if assigned(lEvent) then begin
|
|
fMyCS.Leave;
|
|
try
|
|
dec(lCount);
|
|
lEvent(fData[i]);
|
|
finally
|
|
fMyCs.Enter;
|
|
end;
|
|
end else begin
|
|
lEvent := fData[i].fConnectEvent;
|
|
if assigned(lEvent) then begin
|
|
fMyCs.Leave;
|
|
try
|
|
dec(lCount);
|
|
lEvent(FData[i]);
|
|
finally
|
|
fMyCs.Enter;
|
|
end;
|
|
end;
|
|
end;
|
|
end else
|
|
|
|
if FD_ISSET(fData[i].FSock, fErrorSet) then begin // Connect
|
|
lEvent := fData[i].fConnectEvent;
|
|
lDummy := 4;
|
|
GetSockOpt(fData[i].FSock, SOL_SOCKET, SO_ERROR, Pchar(@fData[i].fConnectResult), lDummy);
|
|
if assigned(lEvent) then begin
|
|
fMyCS.Leave;
|
|
try
|
|
dec(lCount);
|
|
lEvent(fData[i]);
|
|
finally
|
|
fMyCs.Enter;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
fMycs.Leave;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
initialization
|
|
WorkerCS := TCriticalSection.Create;
|
|
Workers := TList.Create;
|
|
finalization
|
|
if SockInitialized then begin
|
|
synsock.WSACleanup;
|
|
DestroySocketInterface;
|
|
end;
|
|
CleanupWorkers;
|
|
Workers.Free;
|
|
WorkerCS.Free;
|
|
end.
|