I have a datasnap server with a vcl forms client. From the client, how can I handle if the server has been say shutdown and restarted with existing client connections? This scenario raises a 10053 EIdSocketError exception.
To replicate, I run up both Server and Client, make a call to the server (I use methods exposed via the DataSnap proxy generator) which succeeeds. I then shut down the server (eg Close the application) and restart it. I then attempt to make a call to the server again.
eg:
CLIENT call
sm := TsvrPolicySearchClient.Create(datClientDB.SQLConnection1.DBXConnection);
try
ds := sm.SearchPolicyByPolicy(40, WCRef, '', 3);
dspPolicyGroup.DataSet := ds;
if cdsPolicyGroup.Active then
cdsPolicyGroup.Refresh
else
cdsPolicyGroup.Open;
finally
sm.Free;
end;
dspPolicyGroup is a TDataSetProvider and cdsPolicyGroup is a TClientDataSet (I just use it locally to "store" my TDataSet result).
SERVER
function TsvrPolicySearch.SearchPolicyByPolicy(AClientId: Integer; WCRefNum, ClientRef: string; SearchMethod: Integer): TDataSet;
begin
spPolicyByWCRef.Close;
spPolicyByWCRef.ParamByName('p_client').AsInteger := AClientId;
spPolicyByWCRef.ParamByName('p_search_method').AsInteger := SearchMethod;
spPolicyByWCRef.ParamByName('p_wc_refno').AsString := WCRefNum;
spPolicyByWCRef.Open;
Result := spPolicyByWCRef;
end;
I would think this is regularly encountered by people seeing that it's quite easy to replicate. Should I place a "Test Connection" call or something first (such as a method TestConnection) before each call to check for EIdSocketError (and equivalent) and handle? Or is it more a design flaw perhaps?
Thanks
My scenario:
Client application connect to DataSnap Server (TCP/IP, remote server)
Client request a DataSet (using DataSnap server methods). TClientDataSet
Client downloaded dataset
Server shutdown (taskkill, close application no matters)
Client request a DataSet againt (ehhrrr Socket Error)
Solution:
On client I catch exception (AppEvents.OnException)
Recognize that is Socket error from connection to DataSnap server
I show dialog window with information that connection is lost. User can click "RETRY" - if so,
Free and re-create DataSnap client module and initialize connection
Try to request dataset (special dataset for connection test) if application catch exception entire process works from the beginning.
After this operation my Client re-connect to DataSnap server and can request dataset using new TCP/IP connection. Of course user can close dialog, but then application is turn off.
I think this can help you. I tried many others solutions but this proved to be the best. Moreover algorithm also supports the loss of connection with the client's fault.
The easiest workaround for this problem is to change the TDSServerClass LifeCycle attribute to Invocation.
This will cause you to have a stateless server, and the server will create a new session per request. But, you will be able to close and reconnect the server without interrupting the clients connection.
Related
I've spent a few weeks googling and trying various changes to the code. I EVEN tried put log file in the SOAP server but no error found in the code.
I have got a SOAP server written in delphi XE. Its very straight forward. Interface has got only one function
type
{ Invokable interfaces must derive from IInvokable }
IIirate = interface(IInvokable)
['{153E0531-5EAB-49E3-B824-62447AF6F0A9}']
{ Methods of Invokable interface must not use the default }
{ calling convention; stdcall is recommended }
Function ProcessRequest(Request: TSOAPAttachment): TSOAPAttachment; stdcall;
end;
I have got one client with proxy enabled pc. Only when he login to my SOAP server its hangs and no one else can access the server unless I recycle the IIS application pool. But I can see soap attachment cache files keeps coming into the server.
Funny thing is if I commented out the ProcessRequest function in interface unit then same situation arise.
You could try soapUI to test against your server (https://www.soapui.org/) to eliminate the possibility that the client app is doing something that is making it hang.
However, one client should not really be able to make it hang. Is the threading model that your SOAP server uses a multi-threaded one? The link has info on COM multi-threading https://learn.microsoft.com/en-us/windows/win32/com/choosing-the-threading-model
Using Delphi XE to build a relatively straightforward database app using Datasnap.
Since some security in my application is handled at the database level, I need to pass a user's SQL credentials from my client app to my Datasnap server.
(I'm trying to make the Datasnap server stateless if possible, so recognise that I will have to do this for every call.)
I'm using ClientDatasets (CDS) on the client side so I could use OnBeforeGetRecords to pass the data in the OwnerData OleVariant from the CDS on the client to the corresponding TDataSetProvider on the server. But that means every single CDS on every data module has to have an event that does this, which seems messy and unwieldy. I can't help feeling there must be a way to pass messages to the server at a higher level than that.
What I'd really like is something like this at the DSServerClass level on the server side:
Procedure TMyServerContainer.MyServerClassCreateInstance(DSCreateInstanceEventObject: TDSCreateInstanceEventObject);
begin
// Server detects request for data from client app
fUsername := GetUsernameFromClientSomehow;
fPassword := GetPasswordFromClientSomehow;
// create data modules and initialise
MyDataModule := TMyDataModule.Create(nil);
MyDataModule.InitialiseWithSQLCredentials(fUsername, fPassword);
DSCreateInstanceEventObject.ServerClassInstance := MyDataModule;
End;
Could the Authentication Manager component help me here? Any other ideas? Or am I stuck with OnBeforeGetRecords?
Many thanks.
You can use the SQL credentials as UserName and Password for connecting to the DataSnap server. These values can be verified in the Authentication Manager and/or simply forwarded to the underlying SQLConnection component for connecting to the SQL server.
The most secure way would be to pass along the user security token (encrypted) and then use integrated security on the server side impersonating in a thread the calling user security context. This way no user/password would ever be sent across the wire. Unluckily while MS/DCE RPC can do this for every call (and DCOM, being built above RPC), Datasnap can't (SPNEGO/GSSAPI/SSPI looks to complex for the guys at Embarcadero, they like simple, unsecure protocols). Otherwise be very careful the way you send credential across the network, they could be easily sniffed unless properly protected.
I would advise you anyway to send them only once, if you need to (and in the most protected way you can), and then store them protected on the server side (suing Windows protected storage facilities), and send back to the client an handle/session token (tied to the originating IP), to be used in subsequent calls instead of resending credentials each time. Informations are cleared when the user logs off or the session timeouts.
How can Indy's TIdTCPClient and TIdTCPServer be used in the following scenario:
Client ---------- initate connection -----------> Server
...
Client <---------------command------------------- Server
Client ----------------response-----------------> Server
...
Client <---------------command------------------- Server
Client ----------------response-----------------> Server
The client initiates the connection, but acts as a "server" (waiting for commands and executing them).
The OnExecute approach of TIdTCPServer does not work well in this case (at least I am not getting it to work well). How could I do this?
I hope the question is clear enough.
There is nothing preventing you from doing this with Indy's TIdTCPServer component.
A TIdTCPServer only sets up the connection. You'll need to implement the rest. So the sequence of the actual sending and receiving can be whatever you want.
Put this code in your TIdTCPServer component's OnExecute event:
var
sName: String;
begin
// Send command to client immediately after connection
AContext.Connection.Socket.WriteLn('What is your name?');
// Receive response from client
sName := AContext.Connection.Socket.ReadLn;
// Send a response to the client
AContext.Connection.Socket.WriteLn('Hello, ' + sName + '.');
AContext.Connection.Socket.WriteLn('Would you like to play a game?');
// We're done with our session
AContext.Connection.Disconnect;
end;
Here's how you can setup the TIdTCPServer really simply:
IdTCPServer1.Bindings.Clear;
IdTCPServer1.Bindings.Add.SetBinding('127.0.0.1', 8080);
IdTCPServer1.Active := True;
This tells the server to listen on the loopback address only, at port 8080. This prevents anyone outside of your computer from connecting to it.
Then, to connect your client, you can go to a Windows command prompt and type the following:
telnet 127.0.0.1 8080
Here's the output:
What is your name?
Marcus
Hello, Marcus.
Would you like to play a game?
Connection to host lost.
Don't have telnet? Here's how to install telnet client on Vista and 7.
Or with a TIdTCP Client, you can do this:
var
sPrompt: String;
sResponse: String;
begin
// Set port to connect to
IdTCPClient1.Port := 8080;
// Set host to connect to
IdTCPClient1.Host := '127.0.0.1';
// Now actually connect
IdTCPClient1.Connect;
// Read the prompt text from the server
sPrompt := IdTCPClient1.Socket.ReadLn;
// Show it to the user and ask the user to respond
sResponse := InputBox('Prompt', sPrompt, '');
// Send user's response back to server
IdTCPClient1.Socket.WriteLn(sResponse);
// Show the user the server's final message
ShowMessage(IdTCPClient1.Socket.AllData);
end;
An important thing to note here is that the ReadLn statements wait until there is data. That's the magic behind it all.
If your commands are textual in nature, then have a look at the TIdCmdTCPClient component, it is specifically designed for situations when the server is sending commands instead of the client. The server can use TIdContext.Connection.IOHandler.WriteLn() or TIdContext.Connection.IOHandler.SendCmd() to send the commands.
When the client connects to the server, the server has an OnConnect event with an AContext: TIdContext parameter.
A property of this is AContext.Connection, which you can store outside of that event (say, in an Array). If you pair it with the IP or better yet a generated Session ID, then reference that Connection by that criteria, you can then have the server send adhoc commands or messages to the client.
Hope this helps!
normally the client and the server side have a thread that is reading incoming telegrams, and sending pending telegrams...but this kind of protocols (send/receive, when and what) depend of the application.
A very good starting point how the client side can be implemented using a thread, listening for messages from the server, is the Indy Telnet client component (TIdTelnet in the Protocols folder).
The Indy telnet client connects to the telnet server and uses only one socket to write and read data. Reading happens in a listener thread.
This design can easily be adapted to build distributed messaging software like chat etc., and also shows how easy the protocol can be decoupled from the network layer using blocking sockets.
With Indy this is not possible by design:
Indy supports only Client-initiated communication, what means the server can only send a response to requests by the client.
The easiest way (but not the smartest) to get what you want is to use a pull-process. Controlled by a timer the clients ask the server if there is a new command. Of course this will cause a lot of traffic-overhead and depending on your pull-intervall there is a delay.
Alternatively you could use another library like ICS (http://www.overbyte.be/eng/products/ics.html)
I have got a lengthy question to ask. First of all Im still very new when it comes to Delphi programming and my experience has beem mostly developing small single user database applications using ADO and an Access database.
I need to take the transition now to a client server application and this is where the problem starts. I decided to use Firebird 2.5 embeded as my database, as it is open source, and it is can be used with the interbase components in Delphi and that multiple clients can access the database simultanously. So I followed the interbase tutorial in Delphi. I managed to connect the client to the server and see the data in the example (While both are running on my pc), but when i tried to move the client to another pc, keeping the server on mine and running it to see if I can connect to the server it gave me the following error.
Exception EIdSocketError in module clientDemo.exe at 0029DCAC. Socket Error # 10061 Connection refused.
I understand that this might be because the host is defined as localhost in the client. But here is my first question. In the TSQLConncetion you can set die hostname under Driver->Hostname. The thing I want to know is how do you do this at run time, as I cannot get the property when I try and make an edit box to allow the user to enter the value and then set it via code like for example:
SQLConncetion1.Driver.Hostname := edtHost.text;
This cannot be done this way and the only way I see you can set the hostname is with the object inspector, but that is not available at runtime and I need to set the hostname on the client when the program is running the first time, so how do you set the hostname/IP address at runtime?
Im using Delphi XE2
There is still a lot of questions to come especially when it comes to deployment, but I will take this piece by piece and I appreciate the advice.
Embedded can't be used by multiple users at the same time (even if it's two applications on the same machine). See here for information on the differences between the three versions. There's also information in another SO question that might help.
As far as specifying a server at runtime, this may help:
procedure TForm1.Button1Click(Sender: TObject);
var
Conn: TSQLConnection;
begin
Conn := TSQLConnection.Create(Self);
try
Conn.DriverName := 'FirebirdConnection';
Conn.Params.Add('User_Name=SYSDBA');
Conn.Params.Add('Password=masterkey');
// Replace the dbname in the next line with the
// value obtained at runtime, as in
// Conn.Params.Add('Database=' + YourNewPathAndDBName);
Conn.Params.Add('Database=C:\FireBirdData\YourDB.fdb');
Conn.Open;
if Conn.Connected then
ShowMessage('Connection successfully made to DB');
finally
Conn.Free;
end;
end;
How can a desktop application communicate with a Windows service under Vista/Windows2008/Windows7? The application needs to send small strings to the service and receive string responses back. Both are written in Delphi 2009. (Please provide sample code also)
The way to go is named pipes, you'll probably have to take a look at the communication across different Integrity levels.
This article explores how to do this in vista. Although it's written in c++ it's just basic Windows API calls, so it should translate fast enough to Delphi.
If you want to search for more on this subject, this communication is called Inter Process Communication, but a better search term is IPC.
Using Indy you can relatively easy create a TCP connection between your apps. Especially if you only need to send string messages. For the client (in your case the desktop application) it's basically
var
Client : TIdTCPClient;
...
Client.Host := 'localhost';
Client.Port := AnyFreePortNumber;
Client.Connect;
Client.IOHandler.Writeln (SomeString);
Response := Client.Readln;
...
Client.Disconnect;
For the server (would be the service in your case)
var
Server : TIdTCPServer;
Binding : TIdSocketHandle;
...
Server.DefaultPort := SameFreePortNumberAsInClient;
Binding := Server.Bindings.Add;
Binding.IP := '127.0.0.1';
Binding.Port := Server.DefaultPort;
Server.OnConnect := HandleConnection;
Server.OnDisconnect := HandleDisconnection;
Server.OnExecute := HandleCommunication;
Server.Active := True;
Just implement the HandleCommunication method. It is called whenever the client decides to send something. Example:
procedure MyClass.HandleCommunication (AContext : TIdContext);
var
Request : String;
begin
Request := AContext.Connection.IOHandler.Readln;
if (Request = Command1) then
HandleCommand1
else if (Request = Command2) then
HandleCommand2
...
end;
IIRC a service is only allowed to have a graphical user interface OR have network access, so this might be a problem if your service needs a GUI (which you should avoid anyway, see this question). I don't know how this is handled in Windwos Vista and later though.
Have a look at the answers in Exchange Data between two apps across PC on LAN which is pretty much the same question nowadays. Local comms via TCP is standard. As I said in my response there, solutions that use "Remote Procedure Call" type interfaces work well. I use RemObjects SDK for this sort of thing, and it makes it easy to expand to control across the network if you wish to later.
Both of these allow you to create a connection that for most of your code is "transparent", and you just call an interface which sends the data over the wire and gets results back. You can then program how you usually do, and forget the details of sockets etc.
You have to change the service user from localsystem to networkservice and then the service can use TCPIP fine. I have several services which use TCPIP for an external control hook. Just make sure your service port is configurable so you can handle any collisions.
A few of my control interfaces are based on XML pages served from an internal HTTP server. This allows me to remotely check on status of the service using any web browser which can reach the port on that machine. The advantage of using HTTP over other methods is that it works well when you need to work over existing network hardware.
If your ONLY going to be communicating locally, then named pipes, mail slots or a memory mapped file might be the best method.
I haven't tried it, but I think you could use named pipes.
I use in my service applications a component set, freeware with sourcecode called Simple IPC.
Search torry.net. It has worked very well in all of my service apps when communicating with a desktop app.
John