Indy TCP Client/Server with the client acting as a server
Asked Answered
S

6

20

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.

Solemnity answered 7/12, 2011 at 12:48 Comment(3)
Maybe you can simulate this behavior with IdTCPClient1.IOHandler.ReadLnWait or IdTCPClient1.IOHandler.WaitFor methods. Otherwise, using TIdCmdTCPCLient could solve your problem.Whiplash
Currently, I'm using Indy 10 in Delphi 2010.Whiplash
good question , Many people need to get data from server without any requests(to reduce traffic) especially when the receiver is behind the NAT, If it is possible , it will become a one way hole punching using TCP(no one in the world yet have succeeded in TCP Hole punching) .Nalchik
G
20

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.

Gambado answered 7/12, 2011 at 15:20 Comment(1)
The 'magic' should be put in its own thread so that the application can continue while the server has nothing to sayEmrich
F
8

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.

Fils answered 7/12, 2011 at 18:3 Comment(0)
M
5

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!

Moreland answered 7/12, 2011 at 13:32 Comment(3)
Yes, and this solution allows for that! The server just needs to store each client's connection so that it can pass messages or commands directly to one or more clients without being limited to responding to client commands first!Moreland
And how will the Client know that it received a command if there is no listening thread on client side?Whiplash
The client has a persisted connection to the server, so of course it has a listening thread on the client side ;) Should point out I've actually made client/server systems with bi-directional communication before (repeatedly).Moreland
P
4

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.

Pyroxenite answered 7/12, 2011 at 14:8 Comment(0)
E
3

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.

Emrich answered 7/12, 2011 at 16:54 Comment(1)
I have implemented a protocol like this before and successfully based it on the telnet client code.Highly
F
-3

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)

Fanlight answered 7/12, 2011 at 14:6 Comment(4)
Is this really a design flaw of Indy? I always thought this was a TCP limitation...Create
Oh, well in that case I guess we are like Tom Cruise in "Mission impossible" because without any pulling from our clients, our server is sending messages to our clients, for example when it is shutting down, or to tell all connected clients of changes in certain data...Multilateral
IIUC TCP/IP communication is: 1. Client connects to Server (the party which initiates the connection is the client by definition), 2. Both parties can send data at any time (because a TCP/IP socket is bidirectional)Emrich
This is not a limitation at all. The server is more than free to send data to the client whenever it wants, and the client is more than welcome to check for incoming data. Indy is extremely capable. It may take a while to learn all about it, but it does all that and then some.Beige

© 2022 - 2024 — McMap. All rights reserved.