[fpc-devel] simpleipc issues
Ondrej Pokorny
lazarus at kluug.net
Tue Sep 29 15:29:08 CEST 2015
On 29.09.2015 12:52, Michael Van Canneyt wrote:
> I added it to fpc/packages/fcl-base. It compiles OK, there are no
> dangerous dependencies except sysutils and classes.
Michael, you added the wrong (old) unit from the bug report, not the
latest simpleipc-compatible one from the mailing list. Please apply the
patch from the attachment!
Thanks
Ondrej
-------------- next part --------------
Index: packages/fcl-base/src/advancedipc.pp
===================================================================
--- packages/fcl-base/src/advancedipc.pp (revision 31886)
+++ packages/fcl-base/src/advancedipc.pp (working copy)
@@ -2,7 +2,13 @@
This file is part of the Free Component Library (FCL)
Copyright (c) 2015 by Ondrej Pokorny
- Unit implementing two-way (request/response) IPC between 1 server and more clients, based on files.
+ Unit implementing two-way (request/response) IPC between 1 server and more
+ clients, based on files.
+ The order of message processing is not deterministic (if there are more
+ pending messages, the server won't process them in the order they have
+ been sent to the server.
+ SendRequest and PostRequest+PeekResponse sequences from 1 client are
+ blocking and processed in correct order.
See the file COPYING.FPC, included in this distribution,
for details about the copyright.
@@ -27,13 +33,14 @@
sysutils, Classes;
const
- HEADER_VERSION = 1;
+ HEADER_VERSION = 2;
type
+ TMessageType = LongInt;
TMessageHeader = packed record
- HeaderVersion: Integer;
+ HeaderVersion: Byte;
FileLock: Byte;//0 = unlocked, 1 = locked
- MsgType: Integer;
+ MsgType: TMessageType;
MsgLen: Integer;
MsgVersion: Integer;
end;
@@ -45,47 +52,58 @@
destructor Destroy; override;
end;
- TIPCBase = class
+ TIPCBase = class(TComponent)
private
FGlobal: Boolean;
FFileName: string;
- FServerName: string;
+ FServerID: string;
FMessageVersion: Integer;
protected
- class function ServerNameToFileName(const aServerName: string; const aGlobal: Boolean): string;
+ class function ServerIDToFileName(const aServerID: string; const aGlobal: Boolean): string;
function GetResponseFileName(const aMsgID: Integer): string;
function GetResponseFileName(const aRequestFileName: string): string;
+ function GetPeekedRequestFileName(const aMsgID: Integer): string;
+ function GetPeekedRequestFileName(const aRequestFileName: string): string;
function GetRequestPrefix: string;
function GetRequestFileName(const aMsgID: Integer): string;
function RequestFileNameToMsgID(const aFileName: string): Integer;
function GetUniqueRequest(out outFileName: string): Integer;
- procedure SetServerName(const aServerName: string); virtual;
+ procedure SetServerID(const aServerID: string); virtual;
procedure SetGlobal(const aGlobal: Boolean); virtual;
- function CanReadMessage(const aFileName: string; out outStream: TStream; out outMsgType, outMsgLen: Integer): Boolean;
- procedure DoPostMessage(const aFileName: string; const aMsgType: Integer; const aStream: TStream);
+ function CanReadMessage(const aFileName: string; out outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer): Boolean;
+ procedure DoPostMessage(const aFileName: string; const aMsgType: TMessageType; const aStream: TStream);
property FileName: string read FFileName;
public
- constructor Create; virtual;
+ class procedure FindRunningServers(const aServerIDPrefix: string;
+ const outServerIDs: TStrings; const aGlobal: Boolean = False);
+ class function ServerRunning(const aServerID: string; const aGlobal: Boolean = False): Boolean; overload;
public
- class procedure FindRunningServers(const aServerNamePrefix: string;
- const outServerNames: TStrings; const aGlobal: Boolean = False);
- class function ServerIsRunning(const aServerName: string; const aGlobal: Boolean = False): Boolean;
- property ServerName: string read FServerName write SetServerName;
+ //ServerID: name/ID of the server. Use only ['a'..'z', 'A'..'Z', '_'] characters
+ property ServerID: string read FServerID write SetServerID;
+ //Global: if true, processes from different users can communicate; false, processes only from current users can communicate
property Global: Boolean read FGlobal write SetGlobal;
+ //MessageVersion: only messages with the same MessageVersion can be delivered between server/client
property MessageVersion: Integer read FMessageVersion write FMessageVersion;
end;
TIPCClient = class(TIPCBase)
- var
+ private
FLastMsgFileName: string;
public
- function PostRequest(const aMsgType: Integer; const aStream: TStream): Integer;//returns ID
- function PeekResponse(const aStream: TStream; var outMsgType: Integer; const aTimeOut: Integer): Boolean;
+ //post request to server, do not wait until request is peeked; returns request ID
+ function PostRequest(const aMsgType: TMessageType; const aStream: TStream): Integer;
+ //send request to server, wait until request is peeked; returns True if request was peeked within the aTimeOut limit
+ function SendRequest(const aMsgType: TMessageType; const aStream: TStream; const aTimeOut: Integer): Boolean;
+ function SendRequest(const aMsgType: TMessageType; const aStream: TStream; const aTimeOut: Integer; out outRequestID: Integer): Boolean;
+ //peek a response from last request from this client
+ function PeekResponse(const aStream: TStream; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
+ //delete last request from this client
procedure DeleteRequest;
- function ServerRunning: Boolean;
+ //check if server is running
+ function ServerRunning: Boolean; overload;
end;
TIPCServer = class(TIPCBase)
@@ -93,33 +111,55 @@
FFileHandle: TFileHandle;
FActive: Boolean;
- function FindFirstRequest(out outFileName: string; out outStream: TStream; out outMsgType, outMsgLen: Integer): Integer;
+ function FindFirstRequest(out outFileName: string; out outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer): Integer;
protected
- procedure SetServerName(const aServerName: string); override;
+ procedure SetServerID(const aServerID: string); override;
procedure SetGlobal(const aGlobal: Boolean); override;
public
- constructor Create; override;
+ constructor Create(aOwner: TComponent); override;
destructor Destroy; override;
public
- function PeekRequest(const aStream: TStream; var outMsgType: Integer): Boolean; overload;
- function PeekRequest(const aStream: TStream; var outMsgID, outMsgType: Integer): Boolean; overload;
- function PeekRequest(const aStream: TStream; var outMsgID, outMsgType: Integer; const aTimeOut: Integer): Boolean; overload;
- procedure PostResponse(const aMsgID: Integer; const aMsgType: Integer; const aStream: TStream);
+ //peek request and read the message into a stream
+ function PeekRequest(const aStream: TStream; out outMsgType: TMessageType): Boolean; overload;
+ function PeekRequest(const aStream: TStream; out outMsgID: Integer; out outMsgType: TMessageType): Boolean; overload;
+ function PeekRequest(const aStream: TStream; out outMsgID: Integer; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean; overload;
+ //only peek request, you have to read/delete the request manually with ReadRequest/DeleteRequest
+ function PeekRequest(out outMsgType: TMessageType): Boolean; overload;
+ function PeekRequest(out outMsgID: Integer; out outMsgType: TMessageType): Boolean; overload;
+ function PeekRequest(out outMsgID: Integer; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean; overload;
+ //read a peeked request (that hasn't been read yet)
+ function ReadRequest(const aMsgID: Integer; const aStream: TStream): Boolean;
+ //delete a peeked request (that hasn't been read yet)
+ procedure DeleteRequest(const aMsgID: Integer);
+ //post response to a request
+ procedure PostResponse(const aMsgID: Integer; const aMsgType: TMessageType; const aStream: TStream);
+
+ //find the highest request ID from all pending requests
function FindHighestPendingRequestId: Integer;
+ //get the pending request count
function GetPendingRequestCount: Integer;
- function StartServer(const aDeletePendingRequests: Boolean = True): Boolean;//returns true if unique and started
- function StopServer(const aDeletePendingRequests: Boolean = True): Boolean;//returns true if stopped
+ //start server: returns true if unique and started
+ function StartServer(const aDeletePendingRequests: Boolean = True): Boolean;
+ //stop server: returns true if stopped
+ function StopServer(const aDeletePendingRequests: Boolean = True): Boolean;
+ //delete all pending requests and responses
procedure DeletePendingRequests;
-
- property Active: Boolean read FActive;//true if started
+ public
+ //true if server runs (was started)
+ property Active: Boolean read FActive;
end;
EICPException = class(Exception);
+resourcestring
+ SErrInvalidServerID = 'Invalid server ID "%s". Please use only alphanumerical characters and underlines.';
+ SErrSetGlobalActive = 'You cannot change the global property when the server is active.';
+ SErrSetServerIDActive = 'You cannot change the server ID when the server is active.';
+
implementation
const
@@ -132,7 +172,8 @@
{ TIPCBase }
function TIPCBase.CanReadMessage(const aFileName: string; out
- outStream: TStream; out outMsgType, outMsgLen: Integer): Boolean;
+ outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer
+ ): Boolean;
var
xFileHandle: TFileHandle;
xHeader: TMessageHeader;
@@ -172,11 +213,6 @@
outMsgLen := xHeader.MsgLen;
end;
-constructor TIPCBase.Create;
-begin
- inherited Create;
-end;
-
function TIPCBase.GetUniqueRequest(out outFileName: string): Integer;
begin
Randomize;
@@ -186,13 +222,13 @@
until not FileExists(outFileName);
end;
-class function TIPCBase.ServerIsRunning(const aServerName: string;
+class function TIPCBase.ServerRunning(const aServerID: string;
const aGlobal: Boolean): Boolean;
var
xServerFileHandle: TFileHandle;
xFileName: String;
begin
- xFileName := ServerNameToFileName(aServerName, aGlobal);
+ xFileName := ServerIDToFileName(aServerID, aGlobal);
Result := FileExists(xFileName);
if Result then
begin//+ check -> we should not be able to access the file
@@ -203,10 +239,10 @@
end;
end;
-class function TIPCBase.ServerNameToFileName(const aServerName: string;
+class function TIPCBase.ServerIDToFileName(const aServerID: string;
const aGlobal: Boolean): string;
begin
- Result := GetTempDir(aGlobal)+aServerName;
+ Result := GetTempDir(aGlobal)+aServerID;
end;
procedure TIPCBase.SetGlobal(const aGlobal: Boolean);
@@ -214,11 +250,11 @@
if FGlobal = aGlobal then Exit;
FGlobal := aGlobal;
- FFileName := ServerNameToFileName(FServerName, FGlobal);
+ FFileName := ServerIDToFileName(FServerID, FGlobal);
end;
procedure TIPCBase.DoPostMessage(const aFileName: string;
- const aMsgType: Integer; const aStream: TStream);
+ const aMsgType: TMessageType; const aStream: TStream);
var
xHeader: TMessageHeader;
xStream: TFileStream;
@@ -226,13 +262,17 @@
xHeader.HeaderVersion := HEADER_VERSION;
xHeader.FileLock := 1;//locking
xHeader.MsgType := aMsgType;
- xHeader.MsgLen := aStream.Size-aStream.Position;
+ if Assigned(aStream) then
+ xHeader.MsgLen := aStream.Size-aStream.Position
+ else
+ xHeader.MsgLen := 0;
xHeader.MsgVersion := MessageVersion;
xStream := TFileStream.Create(aFileName, fmCreate or fmShareExclusive, GLOBAL_RIGHTS);
try
xStream.WriteBuffer(xHeader, SizeOf(xHeader));
- xStream.CopyFrom(aStream, 0);
+ if Assigned(aStream) then
+ xStream.CopyFrom(aStream, 0);
xStream.Position := 0;//unlocking
xHeader.FileLock := 0;
@@ -244,29 +284,42 @@
function TIPCBase.RequestFileNameToMsgID(const aFileName: string): Integer;
begin
- if Length(aFileName) > 8 then
+ //the function prevents all responses/temp files to be handled
+ //only valid response files are returned
+ if (Length(aFileName) > 9) and (aFileName[Length(aFileName)-8] = '-') then
Result := StrToIntDef('$'+Copy(aFileName, Length(aFileName)-7, 8), -1)
else
Result := -1;
end;
-class procedure TIPCBase.FindRunningServers(const aServerNamePrefix: string;
- const outServerNames: TStrings; const aGlobal: Boolean);
+class procedure TIPCBase.FindRunningServers(const aServerIDPrefix: string;
+ const outServerIDs: TStrings; const aGlobal: Boolean);
var
xRec: TRawByteSearchRec;
begin
- if FindFirst(ServerNameToFileName(aServerNamePrefix+'*', aGlobal), faAnyFile, xRec) = 0 then
+ if FindFirst(ServerIDToFileName(aServerIDPrefix+'*', aGlobal), faAnyFile, xRec) = 0 then
begin
repeat
- if (Pos('_', xRec.Name) = 0) and//file that we found is not pending a message
- ServerIsRunning(xRec.Name)
+ if (Pos('-', xRec.Name) = 0) and//file that we found is a pending message
+ ServerRunning(xRec.Name, aGlobal)
then
- outServerNames.Add(xRec.Name);
+ outServerIDs.Add(xRec.Name);
until FindNext(xRec) <> 0;
end;
FindClose(xRec);
end;
+function TIPCBase.GetPeekedRequestFileName(const aMsgID: Integer): string;
+begin
+ Result := GetPeekedRequestFileName(GetRequestFileName(aMsgID));
+end;
+
+function TIPCBase.GetPeekedRequestFileName(const aRequestFileName: string
+ ): string;
+begin
+ Result := aRequestFileName+'-t';
+end;
+
function TIPCBase.GetRequestFileName(const aMsgID: Integer): string;
begin
Result := GetRequestPrefix+IntToHex(aMsgID, 8);
@@ -274,7 +327,7 @@
function TIPCBase.GetRequestPrefix: string;
begin
- Result := FFileName+'_';
+ Result := FFileName+'-';
end;
function TIPCBase.GetResponseFileName(const aMsgID: Integer): string;
@@ -284,22 +337,22 @@
function TIPCBase.GetResponseFileName(const aRequestFileName: string): string;
begin
- Result := aRequestFileName+'_r';
+ Result := aRequestFileName+'-r';
end;
-procedure TIPCBase.SetServerName(const aServerName: string);
+procedure TIPCBase.SetServerID(const aServerID: string);
var
I: Integer;
begin
- if FServerName = aServerName then Exit;
+ if FServerID = aServerID then Exit;
- for I := 1 to Length(aServerName) do
- if not (aServerName[I] in ['a'..'z', 'A'..'Z', '0'..'9']) then
- raise EICPException.Create('You cannot use the "_" character in server name.');
+ for I := 1 to Length(aServerID) do
+ if not (aServerID[I] in ['a'..'z', 'A'..'Z', '0'..'9', '_']) then
+ raise EICPException.CreateFmt(SErrInvalidServerID , [aServerID]);
- FServerName := aServerName;
+ FServerID := aServerID;
- FFileName := ServerNameToFileName(FServerName, FGlobal);
+ FFileName := ServerIDToFileName(FServerID, FGlobal);
end;
{ TIPCClient }
@@ -310,8 +363,8 @@
FLastMsgFileName := '';
end;
-function TIPCClient.PeekResponse(const aStream: TStream;
- var outMsgType: Integer; const aTimeOut: Integer): Boolean;
+function TIPCClient.PeekResponse(const aStream: TStream; out
+ outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
var
xStart: QWord;
xStream: TStream;
@@ -319,7 +372,6 @@
xFileResponse: string;
begin
aStream.Size := 0;
- outMsgType := -1;
Result := False;
xStart := GetTickCount64;
repeat
@@ -337,8 +389,8 @@
until (GetTickCount64-xStart > aTimeOut);
end;
-function TIPCClient.PostRequest(const aMsgType: Integer; const aStream: TStream
- ): Integer;
+function TIPCClient.PostRequest(const aMsgType: TMessageType;
+ const aStream: TStream): Integer;
begin
Result := GetUniqueRequest(FLastMsgFileName);
DeleteFile(GetResponseFileName(FLastMsgFileName));//delete old response, if there is any
@@ -345,9 +397,37 @@
DoPostMessage(FLastMsgFileName, aMsgType, aStream);
end;
+function TIPCClient.SendRequest(const aMsgType: TMessageType;
+ const aStream: TStream; const aTimeOut: Integer): Boolean;
+var
+ xRequestID: Integer;
+begin
+ Result := SendRequest(aMsgType, aStream, aTimeOut, xRequestID);
+end;
+
+function TIPCClient.SendRequest(const aMsgType: TMessageType;
+ const aStream: TStream; const aTimeOut: Integer; out outRequestID: Integer
+ ): Boolean;
+var
+ xStart: QWord;
+ xRequestFileName: string;
+begin
+ outRequestID := PostRequest(aMsgType, aStream);
+ Result := False;
+
+ xRequestFileName := GetRequestFileName(outRequestID);
+ xStart := GetTickCount64;
+ repeat
+ if not FileExists(xRequestFileName) then
+ Exit(True)
+ else if aTimeOut > 20 then
+ Sleep(10);
+ until (GetTickCount64-xStart > aTimeOut);
+end;
+
function TIPCClient.ServerRunning: Boolean;
begin
- Result := ServerIsRunning(ServerName);
+ Result := ServerRunning(ServerID, Global);
end;
{ TReleaseHandleStream }
@@ -376,10 +456,15 @@
FindClose(xRec);
end;
-constructor TIPCServer.Create;
+procedure TIPCServer.DeleteRequest(const aMsgID: Integer);
begin
- inherited Create;
+ DeleteFile(GetPeekedRequestFileName(aMsgID));
+end;
+constructor TIPCServer.Create(aOwner: TComponent);
+begin
+ inherited Create(aOwner);
+
FFileHandle := feInvalidHandle;
end;
@@ -392,7 +477,8 @@
end;
function TIPCServer.FindFirstRequest(out outFileName: string; out
- outStream: TStream; out outMsgType, outMsgLen: Integer): Integer;
+ outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer
+ ): Integer;
var
xRec: TRawByteSearchRec;
begin
@@ -452,14 +538,13 @@
FindClose(xRec);
end;
-function TIPCServer.PeekRequest(const aStream: TStream; var outMsgID,
- outMsgType: Integer): Boolean;
+function TIPCServer.PeekRequest(out outMsgID: Integer; out
+ outMsgType: TMessageType): Boolean;
var
xStream: TStream;
xMsgLen: Integer;
xMsgFileName: string;
begin
- aStream.Size := 0;
outMsgType := -1;
xMsgFileName := '';
outMsgID := FindFirstRequest(xMsgFileName, xStream, outMsgType, xMsgLen);
@@ -466,15 +551,13 @@
Result := outMsgID >= 0;
if Result then
begin
- aStream.CopyFrom(xStream, xMsgLen);
- aStream.Position := 0;
xStream.Free;
- DeleteFile(xMsgFileName);
+ RenameFile(xMsgFileName, GetPeekedRequestFileName(xMsgFileName));
end;
end;
-function TIPCServer.PeekRequest(const aStream: TStream; var outMsgID,
- outMsgType: Integer; const aTimeOut: Integer): Boolean;
+function TIPCServer.PeekRequest(out outMsgID: Integer; out
+ outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
var
xStart: QWord;
begin
@@ -481,7 +564,7 @@
Result := False;
xStart := GetTickCount64;
repeat
- if PeekRequest(aStream, outMsgID, outMsgType) then
+ if PeekRequest(outMsgID, outMsgType) then
Exit(True)
else if aTimeOut > 20 then
Sleep(10);
@@ -488,34 +571,78 @@
until (GetTickCount64-xStart > aTimeOut);
end;
-function TIPCServer.PeekRequest(const aStream: TStream; var outMsgType: Integer
- ): Boolean;
+function TIPCServer.PeekRequest(out outMsgType: TMessageType): Boolean;
var
xMsgID: Integer;
begin
- Result := PeekRequest(aStream, xMsgID{%H-}, outMsgType);
+ Result := PeekRequest(xMsgID, outMsgType);
end;
+function TIPCServer.PeekRequest(const aStream: TStream; out outMsgID: Integer;
+ out outMsgType: TMessageType): Boolean;
+begin
+ Result := PeekRequest(outMsgID, outMsgType);
+ if Result then
+ Result := ReadRequest(outMsgID, aStream);
+end;
+
+function TIPCServer.PeekRequest(const aStream: TStream; out outMsgID: Integer;
+ out outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
+begin
+ Result := PeekRequest(outMsgID, outMsgType, aTimeOut);
+ if Result then
+ Result := ReadRequest(outMsgID, aStream);
+end;
+
+function TIPCServer.PeekRequest(const aStream: TStream; out
+ outMsgType: TMessageType): Boolean;
+var
+ xMsgID: Integer;
+begin
+ Result := PeekRequest(aStream, xMsgID, outMsgType);
+end;
+
procedure TIPCServer.PostResponse(const aMsgID: Integer;
- const aMsgType: Integer; const aStream: TStream);
+ const aMsgType: TMessageType; const aStream: TStream);
begin
DoPostMessage(GetResponseFileName(aMsgID), aMsgType, aStream);
end;
+function TIPCServer.ReadRequest(const aMsgID: Integer; const aStream: TStream
+ ): Boolean;
+var
+ xStream: TStream;
+ xMsgLen: Integer;
+ xMsgType: TMessageType;
+ xFileRequest: string;
+begin
+ aStream.Size := 0;
+ xFileRequest := GetPeekedRequestFileName(aMsgID);
+ Result := CanReadMessage(xFileRequest, xStream, xMsgType, xMsgLen);
+ if Result then
+ begin
+ aStream.CopyFrom(xStream, xMsgLen);
+ xStream.Free;
+ aStream.Position := 0;
+ DeleteFile(xFileRequest);
+ Exit(True);
+ end;
+end;
+
procedure TIPCServer.SetGlobal(const aGlobal: Boolean);
begin
if Active then
- raise EICPException.Create('You cannot change the global property when the server is active.');
+ raise EICPException.Create(SErrSetGlobalActive);
inherited SetGlobal(aGlobal);
end;
-procedure TIPCServer.SetServerName(const aServerName: string);
+procedure TIPCServer.SetServerID(const aServerID: string);
begin
if Active then
- raise EICPException.Create('You cannot change the server name when the server is active.');
+ raise EICPException.Create(SErrSetServerIDActive);
- inherited SetServerName(aServerName);
+ inherited SetServerID(aServerID);
end;
function TIPCServer.StartServer(const aDeletePendingRequests: Boolean): Boolean;
@@ -534,7 +661,7 @@
if FFileHandle<>feInvalidHandle then
FileClose(FFileHandle);
- DeleteFile(FFileName);
+ Result := DeleteFile(FFileName);
FFileName := '';
if aDeletePendingRequests then
More information about the fpc-devel
mailing list