Recent

Author Topic: Handling different clients in indy  (Read 12938 times)

torbente

  • Sr. Member
  • ****
  • Posts: 325
    • Noso Main Page
Handling different clients in indy
« on: August 28, 2014, 09:26:40 am »
Is there a way to send messages to one defined client from the server? My server is working ok until now, since it only process request, but now i need add the functionality to send messages to one client, so it is necessary to link every connected user to their TidContext when they connect (or they soccket, or something) to make everyone reachable if necessary.

Since my server is already able to only allow connections from registered users, im trying to link their username to their TidContext, but i dont know how i could convert it to something usable.

Thanks in advance
Noso Cryptocurrency Main Developer
https://github.com/DevTeamNoso/NosoWallet

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11382
  • FPC developer.
Re: Handling different clients in indy
« Reply #1 on: August 28, 2014, 09:59:44 am »
What Indy server are you using? Usually there is some serverexecute event that is parametrized with Peer context of the user.

However this will only work for protocols that maintain connections. In a connectionless scheme like http it is harder to push events from server to client if the client is not asking.

torbente

  • Sr. Member
  • ****
  • Posts: 325
    • Noso Main Page
Re: Handling different clients in indy
« Reply #2 on: August 28, 2014, 10:33:57 am »
Im using IDTCPServer. I know i can get the user IP ...

var ClientIP : string := acontext.Connection.Socket.Binding.peerip;

But how i send something (in this case, a simple string) to that IP if the client do not ask before?
Noso Cryptocurrency Main Developer
https://github.com/DevTeamNoso/NosoWallet

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11382
  • FPC developer.
Re: Handling different clients in indy
« Reply #3 on: August 28, 2014, 11:01:07 am »
You don't need the IP, but the socket.

It depends on Indy version, but probably simply there are acontext.Connection.Socket.write*() to write to the client socket.

Did you look at the examples? That's a separate download but iirc it had some chat client-server app.

ChrisF

  • Hero Member
  • *****
  • Posts: 542
Re: Handling different clients in indy
« Reply #4 on: August 28, 2014, 04:44:02 pm »
There is sample for an Indy server push action here, for instance:

http://mikejustin.wordpress.com/2014/04/19/indy-10-tidtcpserver-server-side-message-push-example/

It must adapted a little bit in order to work with FPC/Lazarus, as TThread.Queue is not implemented in FPC 2.6.x branches.

Attached a quick conversion of this sample for FPC (it's not really a "full" conversion, but I guess it's enough for a demonstration purpose).
« Last Edit: August 28, 2014, 04:50:01 pm by ChrisF »

torbente

  • Sr. Member
  • ****
  • Posts: 325
    • Noso Main Page
Re: Handling different clients in indy
« Reply #5 on: August 28, 2014, 10:15:58 pm »
That example already knows the TidContext to send the information.

How i could store the socket then? Or Indy servers for many clients only works for answers?
Noso Cryptocurrency Main Developer
https://github.com/DevTeamNoso/NosoWallet

ChrisF

  • Hero Member
  • *****
  • Posts: 542
Re: Handling different clients in indy
« Reply #6 on: August 29, 2014, 02:05:20 am »
You can use your own list of user data, using the onconnect and ondisconnect events, and use the TIdContext.data field to point on them.

or you can derive the TIdcontext class to add your own data.

That's what I've done in this modified sample: I've added a UserName field.

For simplification, this UserName field is filled during the IdContext creation, but of course it should be done instead when analyzing the data received from the client.

torbente

  • Sr. Member
  • ****
  • Posts: 325
    • Noso Main Page
Re: Handling different clients in indy
« Reply #7 on: August 30, 2014, 11:10:31 am »
chrisf,

i solved this creating an array of a record:

Code: [Select]
Userdata = Record
            Name : string[16];
            IdData : TIdContext;
            end; 

Online : array[1..MaxConnectionsAllowed] of UserData;


Then i just need link one name to each connection established and then use the array to manage all connections. Since my server only accepts registered users, i just linked their login to their IdContext.

Ok, now i can send a string to any user using this:

Code: [Select]
Procedure Tform1.SendMessageTo(Userconnected: usertada; message: String);
begin
UserConnnected.IdData.Connection.IOJandler.WriteLn(message);
end;

Example: SendMessageTo(Online[4],'Hello '+Online[4].Name+', you have the virtual socket number 4');

GREAT!

But how i can process the message in the client? IdTCPClient do not have an OnExecute event.
Noso Cryptocurrency Main Developer
https://github.com/DevTeamNoso/NosoWallet

ChrisF

  • Hero Member
  • *****
  • Posts: 542
Re: Handling different clients in indy
« Reply #8 on: August 30, 2014, 03:38:00 pm »
One response is already present in the sample code: use a thread on the client side to wait for incoming data.

Or you can use a timer to periodically check for incoming data.

See Remy Lebeau's answer in this topic:
http://stackoverflow.com/questions/7989133/how-can-i-wait-for-a-string-from-a-server-with-idtcpclient
 

JD

  • Hero Member
  • *****
  • Posts: 1848
Re: Handling different clients in indy
« Reply #9 on: August 30, 2014, 04:23:46 pm »
Indy's IdTCPServer OnExecute method is multi-threaded. Ther server creates a unique thread for every client connected to it. For your application to be thread safe, the client should also communicate with the server via a thread created client side BUT you can get away with not doing this for small applications. So communication between server & client is as simple as

a) client sends message to server (using one of the many Write methods) & waits for a response (using one of the many Read methods)
b) server receives message in client's dedicated thread
c) server processes message & sends reply in client's dedicated thread (IdContext is useful here)
d) client's Read method already set up in (a) above receives the server's response & acts upon it
e) if your client is thread-safe, the reading in (d) above is done in the client side thread and you then Synchronize the thread with your main application GUI to used the response received from the server. See the very basic example below. You can get away with the thread safe client side restriction for very small applications though.

Code: [Select]
procedure TClientThread.Execute;
(* The main client thread that is executed EVERY TIME a client is connected to the server
   All communication (messages/data operations) with the server MUST be done
   INSIDE/THROUGH this thread *)
var
AString: string; 
begin
  {$IFNDEF FPC}
  inherited;
  {$ENDIF}
  // while the thread is not terminated and the client is connected
  while NOT Terminated and FTCPClient.Connected do
  begin
    try
      // Say hello to the server
      FTCPClient.IOHandler.WriteLn("Hello! Are you there?");
      // read server response
      AString := FTCPClient.IOHandler.ReadLn;
      // sync with GUI
    Synchronize(AString);
    finally
      //
      AString := '';
    end; // tryf
    // we need to call Sleep so that this thread will not eat too much CPU
    // 50 miliseconds should be perfect
    // NOTE: we do NOT lose data, we just create a small latency
    Sleep(50);
  end;
end;

You do not need to know each client's IP address. The server handles it internally. I only use client's IP addresses to know whether the client is on the LAN or if the client is external (say in another country for example).

Hope it helps.

JD
« Last Edit: August 30, 2014, 04:29:04 pm by JD »
Windows - Lazarus 2.1/FPC 3.2 (built using fpcupdeluxe),
Linux Mint - Lazarus 2.1/FPC 3.2 (built using fpcupdeluxe)

mORMot; Zeos 8; SQLite, PostgreSQL & MariaDB; VirtualTreeView

torbente

  • Sr. Member
  • ****
  • Posts: 325
    • Noso Main Page
Re: Handling different clients in indy
« Reply #10 on: August 30, 2014, 04:50:22 pm »
Meanwhile im trying to implement all this, i want add that i store the clients IP to avoid multiple connections from the same address (my server do not receive connections from my local LAN never)
Noso Cryptocurrency Main Developer
https://github.com/DevTeamNoso/NosoWallet

JD

  • Hero Member
  • *****
  • Posts: 1848
Re: Handling different clients in indy
« Reply #11 on: August 30, 2014, 05:03:02 pm »
Meanwhile im trying to implement all this, i want add that i store the clients IP to avoid multiple connections from the same address (my server do not receive connections from my local LAN never)

You can easily avoid this by disabling the "Connect" button client-side once a connection with the server has been established. No need for complicated code server side to bounce off multiple same-IP connections. In addition, since a computer can only have a single IP address at any given moment, disabling the a "Connect" button is the easiest & fastest means to do it.

Put the code below in a button's OnClick event (or some other procedure that manages the state of the GUI controls) & it's done.
Code: [Select]
  btnConnect.Enabled := not FTCPClient.Connected;

In addition use the uniqueinstance component http://wiki.lazarus.freepascal.org/UniqueInstance to ensure that only ONE instance of your application can run on a machine at any given time.

The uniqueinstance component is part of the Luipack http://code.google.com/p/luipack/.

How to use uniqueinstance.
http://forum.lazarus.freepascal.org/index.php?topic=16470.0%3bwap2

JD
« Last Edit: August 30, 2014, 05:20:57 pm by JD »
Windows - Lazarus 2.1/FPC 3.2 (built using fpcupdeluxe),
Linux Mint - Lazarus 2.1/FPC 3.2 (built using fpcupdeluxe)

mORMot; Zeos 8; SQLite, PostgreSQL & MariaDB; VirtualTreeView

torbente

  • Sr. Member
  • ****
  • Posts: 325
    • Noso Main Page
Re: Handling different clients in indy
« Reply #12 on: September 02, 2014, 08:19:48 am »
I really love the idea of uniqueinstance. I already downloaded the code and will probably add it to my project.

I just figured out that when i need that my server send one message to one client, it is never necessary to receive an answer, but when it is the client who sent the message, there must be always an anserw. But sometimes, the server could send a message to a client that already sent one request and is waiting for his answer yet. In those situations, the client will receive the server message and will think that is the answer... or not?

Example:

client.toServer.Writeln('What day is today?');
Client,Fromserver.Readln();

But due to connection delays, the server sends this to the client:

Server.ToClient.Writeln('Some rain expected for today');

and then answer the previous request...

Server.ToClient.Writeln('Tuesday');

If the client receives the line A before his answer it will crash.

Any fixes for this?

PS I will include a timer in the client to check if there are messages from the server.
Noso Cryptocurrency Main Developer
https://github.com/DevTeamNoso/NosoWallet

JD

  • Hero Member
  • *****
  • Posts: 1848
Re: Handling different clients in indy
« Reply #13 on: September 03, 2014, 09:12:30 am »
I really love the idea of uniqueinstance. I already downloaded the code and will probably add it to my project.

I just figured out that when i need that my server send one message to one client, it is never necessary to receive an answer, but when it is the client who sent the message, there must be always an anserw. But sometimes, the server could send a message to a client that already sent one request and is waiting for his answer yet. In those situations, the client will receive the server message and will think that is the answer... or not?

Example:

client.toServer.Writeln('What day is today?');
Client,Fromserver.Readln();

But due to connection delays, the server sends this to the client:

Server.ToClient.Writeln('Some rain expected for today');

and then answer the previous request...

Server.ToClient.Writeln('Tuesday');

If the client receives the line A before his answer it will crash.

Any fixes for this?

PS I will include a timer in the client to check if there are messages from the server.

Why is your server sending messages to clients when it has not been asked to? You have to take a consistent structured approach to your application's architecture here. In my opinion, the server should remain silent at all times until a client sends it a message. In my applications, the only exceptions to this are

a) a client is either connecting or disconnecting AND I need to notify other connected clients about this
b) the server is performing a database backup AND clients need to be notified that the server is busy (this backup is triggered by a server-side timer)

In those instances, the message is received client side by a popup tray message using the TPopupNotifier NEVER in the main client GUI. So I keep them separate

i) responses to client messages will be shown in the client side GUI
ii) server initiated messages will be shown in a TPopupNotifier

so there is no confusion between messages on the client side.

JD
« Last Edit: September 03, 2014, 09:18:10 am by JD »
Windows - Lazarus 2.1/FPC 3.2 (built using fpcupdeluxe),
Linux Mint - Lazarus 2.1/FPC 3.2 (built using fpcupdeluxe)

mORMot; Zeos 8; SQLite, PostgreSQL & MariaDB; VirtualTreeView

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Handling different clients in indy
« Reply #14 on: September 03, 2014, 09:38:46 am »
how about an application for real time monitoring of new york stick exchange?  Should the clients keep on requesting new data or is it easier to start a communication and let the server send data as they become available?
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

 

TinyPortal © 2005-2018