I am working on datasnap client server software. When executed, server reads data from my database and keeps it in memory. Every client when connecting to the server, requests the data calling a procedure on the server. But I am having problems with huge memory consumption on server side.
Server lifeCycle set to invocation or session does not affect the huge memory used by server.
In this test, when launching server, it uses about 5MB. Connection a client and getting data from the server, makes the server use another 58MB. For every next client! And this is just with 6 objects. In my real software server gets over 200MB for a client. My old version of the software has 50+ clients running.
I tested this with Delphi XE6, XE7, XE8, 10, 10 with Update 1.
ReportmemoryLeakAtShutdown reports nothing at client, and a very small amount at server, but it is reported as a known bug.
Am I doing something very wrong, or it is a Delphi problem?
Here is my test source:
common unit for server and client:
TOwnedFlag = (ofOwned);
TOwnedFlags = set of TOwnedFlag;
TMarshalList<T: class> = class
private
FList: TArray<T>;
FFlags: TOwnedFlags; // use set for internal flags because sets are not marshalled
public
constructor Create(AList: TArray<T>; AOwnsItems: Boolean = True); overload;
constructor Create; overload;
destructor Destroy; override;
property List: TArray<T> read FList;
end;
TMyClass = class
ID: integer;,
Name: String;
Desc: String;
Desc1: String;
Desc2: String;
Desc3: String;
....
constructor Create;
end;
implementation
constructor TMarshalList<T>.Create(AList: TArray<T>; AOwnsItems: Boolean);
begin
FList := AList;
if AOwnsItems then FFlags := [ofOwned];
end;
constructor TMarshalList<T>.Create;
begin
FFlags := [ofOwned];
end;
destructor TMarshalList<T>.Destroy;
var
LItem: T;
begin
if ofOwned in FFlags then
for LItem in FList do
LItem.Free;
inherited;
end;
constructor TMyClass.create;
var
i: integer;
begin
//this I made just to have objects with noticeable size...
desc := '01101011010110101010101010101010110101010100110100101010010101010101001010101010101010010100100010001111011010010101010110101101'+
'01101011010110101010101010101010110101010100110100101010010101010101001010101010101010010100100010001111011010010101010110101101'+
'01101011010110101010101010101010110101010100110100101010010101010101001010101010101010010100100010001111011010010101010110101101'+
'01101011010110101010101010101010110101010100110100101010010101010101001010101010101010010100100010001111011010010101010110101101';
for I := 1 to 8 do
desc := desc + desc;
desc1 := desc;
desc2 := desc;
....
end;
ServerContainer
MyList: TList<TMyClass>;
procedure TServerContainer.DataModuleCreate(Sender: TObject);
var
c: TMyClass;
I: Integer;
begin
MyList := TList<TMyClass>.create;
for I := 0 to 5 do begin
c := TMyClass.create;
c.ID := i;
c.Name := inttostr(i);
MyList.Add(c);
end;
end;
procedure TServerContainer.DataModuleDestroy(Sender: TObject);
var
I: Integer;
begin
for I := 0 to myList.Count-1 do
myList[i].Free;
myList.Free;
end;
ServerMethods
function TServerMethods.getMarshalList: TMarshalList<TMyClass>;
var
c: TMyClass;
I: Integer;
l: TList<TMyClass>;
begin
l := TList<TMyClass>.create;
for I := 0 to servercontainer.MyList.Count-1 do begin
c := TMyClass.create;
c.ID := servercontainer.MyList[i].ID;
c.Name := servercontainer.MyList[i].Name;
l.Add(c);
end;
Result := TMarshalList<TMyClass>.Create(l.ToArray, True);
l.free;
GetInvocationMetaData.CloseSession := True;
end;
Client
procedure TForm1.Button1Click(Sender: TObject);
var
server: TServerMethodsClient;
c: TMyClass;
I: Integer;
list: TMarshalList<TMyClass>;
begin
ticks := GetTickCount;
memo1.Lines.clear;
server := TServerMethodsClient.Create(SQLConnection1.DBXConnection);
list := server.getMarshalList;
for i := 0 to high(list.List)-1 do begin
c := list.List[i];
end;
server.Free;
end;
you can download the sources here: https://kikimor.com/owncloud/index.php/s/Aw55lBzvFX9Q6tl
Related
I am writing a client / server application. There is one server and several clients.
When connecting a client, the task is to add its IP address to the ListBox, and when disconnecting the client, remove it from the ListBox. Then exchange messages between the client and server.
Three questions arose: when a client connects, its IP address is added to the ListBox, but when disconnected, it is not deleted from there, here is the code:
type
TSimpleClient = class(TObject)
DNS,
Name : String;
ListLink : Integer;
Thread : Pointer;
end;
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
var
Client: TSimpleClient;
begin
Client := TSimpleClient.Create;
Client.DNS := AContext.Connection.Socket.Binding.PeerIP;
Client.ListLink := ListBox1.Items.Count;
Client.Thread := AContext;
ListBox1.Items.Add(Client.DNS);
AContext.Data := Client;
Clients.Add(Client);
end;
procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);
var
Client : TSimpleClient;
begin
sleep(2000);
Client :=Pointer (AContext.Data);
Clients.Delete(Client.ListLink);
ListBox1.Items.Delete(ListBox1.Items.IndexOf(Client.DNS));
Client.Free;
AContext.Data := nil;
end;
The second question, when exchanging messages, the letters in Cyrillic are given as "???", all Google went through it and it was not possible to find an error.
And the third question, on the client is a timer that listens to messages from the server, when the timer is turned on, the client application hangs tight, putting all this into the stream is the same trouble, the code:
if not IdTCPClient1.Connected then
Exit;
s := IdTCPClient1.Socket.ReadLn;
if s <> '' then
Label1.Text := s;
I see quite a few problems with your code.
On the server side, you need to get rid of the TSimpleClient.ListLink field. You are misusing it, causing bad behaviors in your code since you don't keep it updated as clients are added/removed. Think of what happens when you have 2 clients connected, where ListLink is 0 and 1 respectively, and then the 1st client disconnects. The ListLink for the 2nd client will become invalid since you don't decrement it from 1 to 0.
Also TIdTCPServer is a multi-threaded component, its events are fired in the context of worker threads, but your event handler code is not thread-safe. You MUST synchronize with the main UI thread when accessing UI controls from worker threads, and you MUST protect your Clients list from concurrent access across thread boundaries. In this case, you don't really need your own Clients list to begin with as TIdTCPServer has its own thread-safe Contexts list that you can use to access the connected clients.
You are also not handling Unicode at all. By default, Indy's default byte encoding for Unicode strings is US-ASCII, which is why you are getting ? for non-ASCII characters. You can use the IOHandler's DefStringEncoding property to set a different byte encoding, such as IndyTextEncoding_UTF8 (if you are using Delphi 2007 or earlier, you might need to also use the IOHandler's DefAnsiEncoding property to specify how your ANSI strings are converted to/from Unicode. By default, it is set to IndyTextEncoding_OSDefault).
Try something more like this:
type
TSimpleClient = class(TObject)
DNS,
Name : String;
Thread : Pointer;
OutgoingMsgs : TIdThreadSafeStringList;
HasOutgoingMsgs : Boolean;
constructor Create;
destructor Destroy; override;
procedure Queue(const Msg: string);
procedure FlushMsgs;
end;
constructor TSimpleClient.Create;
begin
inherited;
OutgoingMsgs := TIdThreadSafeStringList.Create;
end;
destructor TSimpleClient.Destroy;
begin
OutgoingMsgs.Free;
inherited;
end;
procedure TSimpleClient.Queue(const Msg: string);
var
List: TStringList;
begin
List := OutgoingMsgs.Lock;
try
List.Add(Msg);
HasOutgoingMsgs := True;
finally
OutgoingMsgs.Unlock;
end;
end;
procedure TSimpleClient.FlushMsgs;
var
List: TStringList;
begin
List := OutgoingMsgs.Lock;
try
while List.Count > 0 do
begin
TIdContext(Thread).Connection.IOHandler.WriteLn(List[0]);
List.Delete(0);
end;
HasOutgoingMsgs := False;
finally
OutgoingMsgs.Unlock;
end;
end;
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
var
PeerIP: string;
Client: TSimpleClient;
begin
PeerIP := AContext.Binding.PeerIP;
Client := TSimpleClient.Create;
Client.DNS := PeerIP;
Client.Thread := AContext;
AContext.Data := Client;
TThread.Queue(nil,
procedure
begin
ListBox1.Items.AddObject(PeerIP, Client);
end
);
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;
procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);
var
Client : TSimpleClient;
begin
Client := TSimpleClient(AContext.Data);
try
TThread.Queue(nil,
procedure
var
Index: Integer;
begin
Index := ListBox1.Items.IndexOfObject(Client);
if Index <> -1 then
ListBox1.Items.Delete(Index);
end;
);
finally
{ The anonymous procedure being passed to TThread.Queue() above captures
the Client variable itself, not its value. On ARC platforms, we need to
prevent Free() setting the variable to nil before it can be passed to
IndexOfObject(), and also because IndexOfObject() expects a live object
anyway. ARC will free the object when the anonymous procedure exits. On
non-ARC platforms, it is OK to Free() the object here, the variable will
not change value, and IndexOfObject() does not need a live object... }
{$IFNDEF AUTOREFCOUNT}
Client.Free;
{$ENDIF}
AContext.Data := nil;
end;
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
Client: TSimpleClient;
begin
Client := TSimpleClient(AContext.Data);
if Client.HasOutgoingMsgs then
Client.FlushMsgs
else
Sleep(100);
end;
procedure TForm1.SendMessageToClient(Client: TSimpleClient; const Msg: string);
var
List: TIdContextList;
begin
List := IdTCPServer1.Contexts.LockList;
try
if List.IndexOf(TIdContext(Client.Thread)) <> -1 then // still connected?
Client.Queue(Msg);
finally
IdTCPServer1.Contexts.UnlockList;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Index: Integer;
Msg: string;
Client: TSimpleClient;
begin
Index := ListBox1.ItemIndex;
if Index = -1 then Exit;
Msg := Edit1.Text;
if Msg = '' then Exit;
Client := TSimpleClient(ListBox1.Items.Objects[Index]);
SendMessageToClient(Client, Msg);
end;
Alternatively, you can derive TSimpleClient from TIdServerContext and get rid of the Thread field altogether:
type
TSimpleClient = class(TIdServerContext)
DNS,
Name : String;
OutgoingMsgs : TIdThreadSafeStringList;
HasOutgoingMsgs : Boolean;
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
procedure Queue(const Msg: string);
procedure FlushMsgs;
end;
constructor TSimpleClient.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
inherited Create(AConnection, AYarn, AList);
OutgoingMsgs := TIdThreadSafeStringList.Create;
end;
destructor TSimpleClient.Destroy;
begin
OutgoingMsgs.Free;
inherited;
end;
procedure TSimpleClient.Queue(const Msg: string);
var
List: TStringList;
begin
List := OutgoingMsgs.Lock;
try
List.Add(Msg);
HasOutgoingMsgs := True;
finally
OutgoingMsgs.Unlock;
end;
end;
procedure TSimpleClient.FlushMsgs;
var
List: TStringList;
begin
List := OutgoingMsgs.Lock;
try
while List.Count > 0 do
begin
Self.Connection.IOHandler.WriteLn(List[0]);
List.Delete(0);
end;
HasOutgoingMsgs := False;
finally
OutgoingMsgs.Unlock;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
IdTCPServer1.ContextClass := TSimpleClient;
end;
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
var
PeerIP: string;
Client: TSimpleClient;
begin
PeerIP := AContext.Binding.PeerIP;
Client := TSimpleClient(AContext);
Client.DNS := PeerIP;
TThread.Queue(nil,
procedure
begin
ListBox1.Items.AddObject(PeerIP, Client);
end
);
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;
procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);
var
Client : TSimpleClient;
begin
Client := TSimpleClient(AContext);
TThread.Queue(nil,
procedure
var
Index: Integer;
begin
Index := ListBox1.Items.IndexOfObject(Client);
if Index <> -1 then
ListBox1.Items.Delete(Index);
end;
);
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
Client: TSimpleClient;
begin
Client := TSimpleClient(AContext);
if Client.HasOutgoingMsgs then
Client.FlushMsgs
else
Sleep(100);
end;
procedure TForm1.SendMessageToClient(Client: TSimpleClient; const Msg: string);
var
List: TIdContextList;
begin
List := IdTCPServer1.Contexts.LockList;
try
if List.IndexOf(TIdContext(Client)) <> -1 then // still connected?
Client.Queue(Msg);
finally
IdTCPServer1.Contexts.UnlockList;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Index: Integer;
Msg: string;
Client: TSimpleClient;
begin
Index := ListBox1.ItemIndex;
if Index = -1 then Exit;
Msg := Edit1.Text;
if Msg = '' then Exit;
Client := TSimpleClient(ListBox1.Items.Objects[Index]);
SendMessageToClient(Client, Msg);
end;
On the client side, you are reading from the socket in the main UI thread, but Indy uses blocking sockets, and so its reading methods will block the calling thread until the requested data arrives. DON'T block the main UI thread! Read only if there is actually something available to read, or else move the reading into a separate worker thread. For example:
IdTCPClient1.Connect;
IdTCPClient1.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
...
IdTCPClient1.Disconnect;
...
procedure TForm1.Timer1Timer(Sender: TObject);
var
s: string;
begin
if IdTCPClient1.Connected and (not IdTCPClient1.IOHandler.InputBufferIsEmpty) then
begin
s := IdTCPClient1.IOHandler.ReadLn;
if s <> '' then
Label1.Text := s;
end;
end;
Alternatively:
type
TReadingThread = class(TThread)
protected
procedure Execute; override;
end;
procedure TReadingThread.Execute;
var
s: String;
begin
while not Terminated do
begin
s := Form1.IdTCPClient1.IOHandler.ReadLn;
if s <> '' then
begin
TThread.Queue(nil,
procedure
begin
Form1.Label1.Text := s;
end
);
end;
end;
end;
...
var
ReadingThread: TReadingThread = nil;
...
IdTCPClient1.Connect;
IdTCPClient1.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
ReadingThread := TReadingThread.Create(False);
...
ReadingThread.Terminate;
try
IdTCPClient1.Disconnect;
finally
ReadingThread.WaitFor;
ReadingThread.Free;
end;
Thank you so much Remy, your answer really helped me sort out my problem. I targeted Windows and Android platforms. I fixed your code a little and it worked for me:
type
TSimpleClient = class(TObject)
DNS,
Name : String;
Thread : Pointer;
OutgoingMsgs : TIdThreadSafeStringList;
HasOutgoingMsgs : Boolean;
constructor Create;
destructor Destroy; override;
procedure Queue(const Msg: string);
procedure FlushMsgs;
end;
constructor TSimpleClient.Create;
begin
inherited;
OutgoingMsgs := TIdThreadSafeStringList.Create;
end;
destructor TSimpleClient.Destroy;
begin
OutgoingMsgs.Free;
inherited;
end;
procedure TSimpleClient.Queue(const Msg: string);
var
List: TStringList;
Client: TSimpleClient;
begin
List := OutgoingMsgs.Lock;
try
List.Add(Msg);
HasOutgoingMsgs := True;
Client.FlushMsgs;
finally
OutgoingMsgs.Unlock;
end;
end;
procedure TSimpleClient.FlushMsgs;
var
List: TStringList;
begin
List := OutgoingMsgs.Lock;
try
while List.Count > 0 do
begin
TIdContext(Thread).Connection.IOHandler.WriteLn(List[0]);
List.Delete(0);
end;
HasOutgoingMsgs := False;
finally
OutgoingMsgs.Unlock;
end;
end;
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
var
PeerIP: string;
Client: TSimpleClient;
begin
PeerIP := AContext.Binding.PeerIP;
Client := TSimpleClient.Create;
Client.DNS := PeerIP;
Client.Thread := AContext;
AContext.Data := Client;
TThread.Queue(nil,
procedure
begin
ListBox1.Items.AddObject(PeerIP, Client);
end
);
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;
procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);
var
Client : TSimpleClient;
begin
Client := TSimpleClient(AContext.Data);
try
TThread.Queue(nil,
procedure
var
Index: Integer;
begin
Index := ListBox1.Items.IndexOfObject(Client);
if Index <> -1 then
ListBox1.Items.Delete(Index);
end;
);
finally
{ The anonymous procedure being passed to TThread.Queue() above captures
the Client variable itself, not its value. On ARC platforms, we need to
prevent Free() setting the variable to nil before it can be passed to
IndexOfObject(), and also because IndexOfObject() expects a live object
anyway. ARC will free the object when the anonymous procedure exits. On
non-ARC platforms, it is OK to Free() the object here, the variable will
not change value, and IndexOfObject() does not need a live object... }
{$IFNDEF AUTOREFCOUNT}
Client.Free;
{$ENDIF}
AContext.Data := nil;
end;
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
Client: TSimpleClient;
begin
Client := TSimpleClient(AContext.Data);
if Client.HasOutgoingMsgs then
Client.FlushMsgs
else
Sleep(100);
end;
procedure TForm1.SendMessageToClient(Client: TSimpleClient; const Msg: string);
var
List: TIdContextList;
begin
List := IdTCPServer1.Contexts.LockList;
try
if List.IndexOf(TIdContext(Client.Thread)) <> -1 then // still connected?
Client.Queue(Msg);
finally
IdTCPServer1.Contexts.UnlockList;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Index: Integer;
Msg: string;
Client: TSimpleClient;
begin
Index := ListBox1.ItemIndex;
if Index = -1 then Exit;
Msg := Edit1.Text;
if Msg = '' then Exit;
Client := TSimpleClient(ListBox1.Items.Objects[Index]);
SendMessageToClient(Client, Msg);
end;
I added a call to the FlushMsgs method from the TSimpleClient.Queue procedure and messages started to be sent, the list of clients is updated every time clients are connected and disconnected, and the server stopped hanging. Thanks again Remy, you helped a lot to speed up the development, golden man.
After some time my client thread stops receiving/sending commands from/to TIdTcpServer.
Here is the client side thread I copied from an example from Remy:
Tested locally and it doesn't happen, only on a running environment the error occurs...
type
TDataEvent = procedure(const LBuffer: TIdBytes) of object;
TReadingThread = class(TThread)
private
FClient : TIdTCPClient;
FData : TIdBytes;
FOnData : TDataEvent;
procedure DataReceived;
protected
procedure Execute; override;
public
constructor Create(AClient: TIdTCPClient); reintroduce;
property OnData: TDataEvent read FOnData write FOnData;
end;
constructor TReadingThread.Create(AClient: TIdTCPClient);
begin
inherited Create(True);
FClient := AClient;
end;
procedure TReadingThread.Execute;
begin
while not Terminated do
begin
Form1.Cliente.IOHandler.ReadBytes(FData, szProtocol, False);
if (FData <> nil) and Assigned(FOnData) then Synchronize(DataReceived);
end;
end;
procedure TReadingThread.DataReceived;
begin
if Assigned(FOnData) then FOnData(FData);
end;
procedure TForm1.DataReceived(const LBuffer: TIdBytes);
type
PTBytes = ^TBytes;
PTIdBytes = ^TIdBytes;
var
LDataSize : Integer;
LProtocol : TProtocol;
LBuffer2 : TBytes;
LProtocol2 : TProtocol;
begin
LProtocol := BytesToProtocol(PTBytes(#LBuffer)^);
case LProtocol.Command of
cmdHWID:
begin
HWID := LProtocol.Sender.HWID;
end;
cmdPing:
begin
InitProtocol(LProtocol2);
LProtocol2.Command := cmdPing;
LProtocol2.Sender.PBack := GetTickCount;
LBuffer2 := ProtocolToBytes(LProtocol2);
Form1.Cliente.IOHandler.Write(PTIdBytes(#LBuffer2)^);
ClearBuffer(LBuffer2);
end;
end;
end;
For a while all works perfectly, but after some time, the client side stops receiving/sending. The connection to the server is seems to be still open.
function to find connection by ip:
list := IdTCPServer1.Contexts.LockList;
try
for i := 0 to list.Count - 1 do
begin
ctx := TIdContext(list[i]);
if ctx.Binding.PeerIP = Edit9.Text then
begin
TLog.AddMsg('IP FOUND');
Achou := True;
Cliente := TClientContext(ctx);
SerialCn := Cliente.Client.HWID;
IpCn := Cliente.Client.IP;
break;
end;
end;
finally
IdTCPServer1.Contexts.UnlockList;
end;
I have 2 years old project which I am rebooting back to life. I have debug window which shows different information, and one part of them is that it serializes various google protobuf objects and shows them.
Here is how typical protobuf class looks like:
// Generated by the protocol buffer compiler. DO NOT EDIT!
// Source: message.proto
unit Poker.Protobufs.Objects.PingParams;
interface
uses
System.SysUtils,
{$IFNDEF FPC} System.Generics.Collections {$ELSE} Contnrs {$ENDIF},
pbOutput, Poker.Protobufs.Objects.Base, Poker.Protobufs.Reader, Poker.Types;
type
TPB_PingParams = class(TProtobufBaseObject)
private
const
kUptimeFieldNumber = 1;
var
FUptime: UInt32;
FHasBits: UINT32;
procedure set_has_Uptime;
procedure clear_has_Uptime;
procedure SetUptime(const AValue: UInt32);
public
constructor Create(const AFrom: TPB_PingParams; const ALightweight: Boolean = FALSE); overload;
destructor Destroy; override;
procedure LoadFromProtobufReader(const AProtobufReader: TProtobufReader; const ASize: Integer); override;
procedure MergeFrom(const AFrom: TPB_PingParams);
procedure Clear;
function IsInitialized: Boolean; override;
// required uint32 Uptime = 1;
function has_Uptime: Boolean;
procedure clear_Uptime;
property Uptime: UInt32 read FUptime write SetUptime;
end;
TPB_PingParamsList = class(TObjectList<TPB_PingParams>)
procedure Assign(const APB_PingParamsList: TList<TPB_PingParams>);
end;
implementation
uses
pbPublic;
constructor TPB_PingParams.Create(const AFrom: TPB_PingParams; const ALightweight: Boolean = FALSE);
begin
inherited Create(ALightweight);
MergeFrom(AFrom);
end;
destructor TPB_PingParams.Destroy;
begin
inherited;
end;
procedure TPB_PingParams.LoadFromProtobufReader(const AProtobufReader: TProtobufReader; const ASize: Integer);
var
tag, field_number, wire_type, endpos: Integer;
begin
endpos := AProtobufReader.getPos + ASize;
while (AProtobufReader.getPos < endpos) and
(AProtobufReader.GetNext(tag, wire_type, field_number)) do
case field_number of
kUptimeFieldNumber: begin
Assert(wire_type = WIRETYPE_VARINT);
FUptime := AProtobufReader.readUInt32;
set_has_Uptime;
end;
else
AProtobufReader.skipField(tag);
end;
end;
procedure TPB_PingParams.MergeFrom(const AFrom: TPB_PingParams);
begin
if AFrom.has_Uptime then
SetUptime(AFrom.Uptime);
end;
function TPB_PingParams.IsInitialized: Boolean;
begin
if (FHasBits and $1) <> $1 then
Exit(FALSE);
Exit(TRUE);
end;
procedure TPB_PingParams.clear_Uptime;
begin
FUptime := 0;
clear_has_Uptime;
end;
function TPB_PingParams.has_Uptime: Boolean;
begin
result := (FHasBits and 1) > 0;
end;
procedure TPB_PingParams.set_has_Uptime;
begin
FHasBits := FHasBits or 1;
end;
procedure TPB_PingParams.clear_has_Uptime;
begin
FHasBits := FHasBits and not 1;
end;
procedure TPB_PingParams.SetUptime(const AValue: UInt32);
begin
if not Lightweight then
Assert(not has_Uptime);
FUptime := AValue;
if not Lightweight then
ProtobufOutput.writeUInt32(kUptimeFieldNumber, AValue);
set_has_Uptime;
end;
procedure TPB_PingParams.Clear;
begin
if FHasBits = 0 then
Exit;
clear_Uptime;
end;
procedure TPB_PingParamsList.Assign(const APB_PingParamsList: TList<TPB_PingParams>);
var
pbobj: TPB_PingParams;
begin
Clear;
for pbobj in APB_PingParamsList do
Add(TPB_PingParams.Create(pbobj, TRUE));
end;
end.
And my serialization function:
function SerializeObject(const AObject: TObject): String;
var
t: TRttiType;
p: TRttiProperty;
properties: TArray<TRttiProperty>;
method: TRttiMethod;
begin
result := '';
if not Assigned(AObject) then
Exit;
t := TRttiContext.Create.GetType(AObject.ClassType);
properties := t.GetProperties;
for p in properties do
begin
method := t.GetMethod(Format('has_%s', [p.Name]));
if (Assigned(method)) and
(method.Invoke(AObject, []).AsBoolean) then
result := result + Format('%s: %s; ', [p.Name, ValueToStr(p, p.GetValue(AObject))]);
end;
end;
It is specifically designed to serialize fields that begin with has_ in protobuf objects. Now, I didn't change anything in the code over last 2 years, and this was working before. But now it doesn't. Line properties = t.GetProperties returns empty array for my protobuf classes.
My guess is that I had some globally defined compiler directive which allowed me to serialize public methods in the class. But I cannot figure out which one.
If I put {$M+} in front of my protobuf classes, and move methods to published, it works (kinda). But this worked before just like it is shown in the sources, without any {$M+} directives or similar. So I'm curious what I miss.
Compiler is same as before, XE2.
I've a sample ISAPI module hosts on IIS7 on Windows Vista Ultimate in Virtual Machine. According to the IIS 7.0 Editions and Windows, the simultaneous request execution limit is ten for Windows Vista Ultimate. I write a sample application for testing the IIS7 simultaneous request execution limit:
procedure TForm1.Button1Click(Sender: TObject);
const c_Max = 20;
var P: TProc<string>;
o: TThread;
i: integer;
A: array of TThread;
begin
P :=
procedure(aValue: string)
begin
Memo1.Lines.Add(aValue);
end;
SetLength(A, c_Max);
for i := Low(A) to High(A) do begin
o := TMyThread.Create(P, edHostName.Text, Format('test%d', [i + 1]), True);
o.FreeOnTerminate := True;
A[i] := o;
end;
for o in A do
o.Start;
end;
constructor TMyThread.Create(const aMethod: TProc<string>; const aHostName,
aValue: string; const aCreateSuspended: boolean);
begin
inherited Create(aCreateSuspended);
FMethod := aMethod;
FHostName := aHostName;
FValue := aValue;
end;
procedure TMyThread.Execute;
var C: TSQLConnection;
o: TServerMethods1Client;
S: string;
begin
C := TSQLConnection.Create(nil);
C.DriverName := 'DataSnap';
C.LoginPrompt := False;
with C.Params do begin
Clear;
Values[TDBXPropertyNames.DriverUnit] := 'Data.DBXDataSnap';
Values[TDBXPropertyNames.HostName] := FHostName;
Values[TDBXPropertyNames.Port] := IntToStr(80);
Values[TDBXPropertyNames.CommunicationProtocol] := 'http';
Values[TDBXPropertyNames.URLPath] := 'MyISAPI/MyISAPI.dll';
end;
S := Format('%d=', [ThreadID]);
try
try
C.Open;
o := TServerMethods1Client.Create(C.DBXConnection);
try
S := S.Format('%s%s', [S, o.EchoString(FValue)]);
finally
o.Free;
end;
except
on E: Exception do
S := S.Format('%s%s', [S, E.Message]);
end;
finally
Synchronize(
procedure
begin
FMethod(S);
end
);
C.Free;
end;
end;
When i click the Button1, the Memo1 displays 20 lines of string (ThreadID=Result of EchoString), even though i change the constant variable c_Max to 500, i still can't get any line of string (ThreadID=Exception message) displays on Memo1. My question is how to test IIS7 simultaneous request execution limit?
I have a legacy delphi 2007 application that sends email alarms via TurboPower Internet Professional 1.15 (tpipro). I have recently revisited the application to find that the email send no longer is working because of the TLS/SSL requirements from most email servers. Now my question is where to go from here.
I Have Delphi XE2, but really have no desire to take the time to update my application to work on this ide. It has many library dependencies and so forth.
Is there a 3rd party email client that is up to date that will work on Delphi 2007? Or perhaps a .dll that could be used?
You can use the Indy library which is included in delphi, these components support TLS and SSL (take a look to the TIdSmtp Component), you can find the last version of Indy Here.
Just to give you some more options
You could also try IPWorks its not free thou, you can find it Here or you might wanna look at ICS (Internet Component Suite) Which is freeware and you can find that Here
Indy is the obvious choice as it comes installed with Delphi XE2
Just did this yesterday (you can replace my own classes with VCL classes to get it to work):
unit SmtpClientUnt;
interface
uses
Classes, IdSslOpenSsl, IdSmtp, CsiBaseObjectsUnt, DevExceptionsUnt;
type
ESmtpClient = class(EDevException);
TSmtpClient = class sealed(TCsiBaseObject)
private
FHostName: string;
FIdSmtpClient: TIdSmtp;
FIoHandler: TIdSslIoHandlerSocketOpenSsl;
FUseTls: Boolean;
protected
procedure CheckIsOpen(const pEventAction: string);
function GetHostName: string; virtual;
function GetIsOpen: Boolean; virtual;
function GetObjectName: string; override;
public
const LC_SMTP_CLIENT = 'SMTP Client';
constructor Create(const pHostName: string; pUseTls: Boolean = False);
destructor Destroy; override;
procedure Close;
procedure Open(const pUserName: string = ''; const pPassword: string = '');
procedure Reconnect;
procedure SendMessage(pToAddresses: TStrings; const pFromAddress: string;
const pSubject: string; const pBody: string;
pAttachmentFiles: TStrings = nil);
property HostName: string read GetHostName;
property IsOpen: Boolean read GetIsOpen;
end;
implementation
uses
SysUtils, IdAttachmentFile, IdEmailAddress, IdExplicitTlsClientServerBase, IdMessage,
CsiExceptionsUnt, CsiGlobalsUnt, CsiSingletonManagerUnt, CsiStringsUnt;
{ TSmtpClient }
procedure TSmtpClient.CheckIsOpen(const pEventAction: string);
begin
if not IsOpen then
raise ESmtpClient.Create('Cannot ' + pEventAction +
' while the SMTP client is not open', slError, 1,
ObjectName);
end;
procedure TSmtpClient.Close;
begin
if IsOpen then begin
CsiGlobals.AddLogMsg('Closing SMTP client', LC_SMTP_CLIENT, llVerbose, ObjectName);
FIdSmtpClient.Disconnect;
end;
end;
constructor TSmtpClient.Create(const pHostName: string; pUseTls: Boolean);
begin
FHostName := pHostName;
FUseTls := pUseTls;
inherited Create;
if FHostName = '' then
raise ESmtpClient.Create('Cannot create SMTP client - empty host name', slError, 2,
ObjectName);
FIdSmtpClient := TIdSmtp.Create(nil);
FIdSmtpClient.Host := pHostName;
if FUseTls then begin
FIoHandler := TIdSslIoHandlerSocketOpenSsl.Create(nil);
FIdSmtpClient.IoHandler := FIoHandler;
FIdSmtpClient.UseTls := utUseRequireTls;
end;
end;
destructor TSmtpClient.Destroy;
begin
Close;
if FUseTls and Assigned(FIdSmtpClient) then begin
FIdSmtpClient.IoHandler := nil;
FreeAndNil(FIoHandler);
end;
FreeAndNil(FIdSmtpClient);
inherited;
end;
function TSmtpClient.GetHostName: string;
begin
if Assigned(FIdSmtpClient) then
Result := FIdSmtpClient.Host
else
Result := FHostName;
end;
function TSmtpClient.GetIsOpen: Boolean;
begin
Result := Assigned(FIdSmtpClient) and FIdSmtpClient.Connected;
end;
function TSmtpClient.GetObjectName: string;
var
lHostName: string;
begin
Result := inherited GetObjectName;
lHostName := HostName;
if lHostName <> '' then
Result := Result + ' ' + lHostName;
end;
procedure TSmtpClient.Open(const pUserName: string; const pPassword: string);
begin
if not IsOpen then begin
with FIdSmtpClient do begin
Username := pUserName;
Password := pPassword;
Connect;
end;
CsiGlobals.AddLogMsg('SMTP client opened', LC_SMTP_CLIENT, llVerbose, ObjectName);
end;
end;
procedure TSmtpClient.Reconnect;
begin
Close;
Open;
end;
procedure TSmtpClient.SendMessage(pToAddresses: TStrings; const pFromAddress: string;
const pSubject: string; const pBody: string;
pAttachmentFiles: TStrings);
var
lMessage: TIdMessage;
lAddress: string;
lName: string;
lIndex: Integer;
lAddressItem: TIdEMailAddressItem;
lAttachmentFile: TIdAttachmentFile;
lFileName: string;
begin
CheckIsOpen('send message');
lMessage := TIdMessage.Create(nil);
try
with lMessage do begin
CsiSplitFirstStr(pFromAddress, ',', lAddress, lName);
From.Address := lAddress;
From.Name := lName;
Subject := pSubject;
Body.Text := pBody;
end;
for lIndex := 0 to pToAddresses.Count - 1 do begin
lAddressItem := lMessage.Recipients.Add;
CsiSplitFirstStr(pToAddresses.Strings[lIndex], ',', lAddress, lName);
lAddressItem.Address := lAddress;
lAddressItem.Name := lName;
end;
if Assigned(pAttachmentFiles) then
for lIndex := 0 to pAttachmentFiles.Count - 1 do begin
lAttachmentFile := TIdAttachmentFile.Create(lMessage.MessageParts);
lFileName := pAttachmentFiles.Strings[lIndex];
lAttachmentFile.StoredPathName := lFileName;
lAttachmentFile.FileName := lFileName;
end;
FIdSmtpClient.Send(lMessage);
finally
lMessage.Free;
end;
end;
procedure InitialiseUnit;
begin
CsiAllCapWords.AddString('SMTP');
end;
initialization
CsiSingletonManager.RegisterHook(InitialiseUnit, nil);
end.
Here are the Demo codes:
http://www.indyproject.org/sockets/demos/index.en.aspx
IdPOP3 / IdSMTP / IdMessage