2

In Socket applications programmed by TCPServer/Client components, usually we active server side, then connect client to server, and when we need to get or send data from one side to other, first we send a command from client to server and a communication will starts.

But the problem is that always we need to start conversation from client side!

I want to ask is any idea for start conversation randomly from server side without client side request?

I need this functionality for notify client(s) from server side. for example, when a registered user (client-side) connected to server, other connected users (on other client-sides), a notification must send from server to all users (like Yahoo Messenger).

I'm using TIdCmdTCPServer and TIdTCPClient components

Farshad H.
  • 303
  • 4
  • 16
  • 2
    And who or what would you connect to? The connection being lost is a sure sign the client is not able, or does not want to continue talking to you... The client program may have crashed or have been killed through the task manager. When the client is able and willing, it will re-establish the connection with the server. – Marjan Venema Dec 15 '12 at 10:32
  • @MarjanVenema Yeah dear Marjan! I've got this. but I've been search for some simple method. It appears there is no simplest way to doing that! Thanks Specially :) – Farshad H. Dec 15 '12 at 10:55

4 Answers4

8

You are using TIdCmdTCPServer. By definition, it sends responses to client-issued commands. For what you are asking, you should use TIdTCPServer instead, then you can do whatever you want in the TIdTCPServer.OnExecute event.

What you ask for is doable, but its implementation depends on your particular needs for your protocol.

If you just want to send unsolicited server-to-client messages, and never responses to client-to-server commands, then the implementation is fairly straight forward. Use TIdContext.Connection.IOHandler when needed. You can loop through existing clients in the TIdTCPServer.Contexts list, such as inside the TIdTCPServer.OnConnect and TIdTCPServer.OnDisconnect events. On the client side, you need a timer or thread to check for server messages periodically. Look at TIdCmdTCPClient and TIdTelnet for examples of that.

But if you need to mix both client-to-server commands and unsolicited server-to-client messages on the same connection, you have to design your protocol to work asynchronously, which makes the implementation more complex. Unsolicited server messages can appear at anytime, even before the response to a client command. Commands need to include a value that is echoed in the response so clients can match up responses, and the packets need to be able to differentiate between a response and an unsolicited message. You also have to give each client its own outbound queue on the server side. You can use the TIdContext.Data property for that. You can then add server messages to the queue when needed, and have the OnExecute event send the queue periodically when it is not doing anything else. You still need a timer/thread on the client side, and it needs to handle both responses to client commands and unsolicited server messages, so you can't use TIdConnection.SendCmd() or related methods, as it won't know what it will end up reading.

I have posted examples of both approaches in the Embarcadero and Indy forums many times before.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Very very very good answer! I used a thread with 2 sec sleep time in my client. client is sending some checking command every 2 sec to my server to check if any notification is available for current client. and in server-side, I used an arrayed structure to saving notifications for my client(s). it works correctly, but sometimes there is some serious exceptions in client and server :( I discovered now that programming in TCD protocol is very complicated, and I need some more examples, can you help me about this? – Farshad H. Dec 16 '12 at 05:24
  • try TCP1.SendCmd('checkmynotif'); TCP1.Socket.WriteLn(IntToStr(frmRecieverMain.UserID)); str := TCP1.Socket.ReadLn; if Pos('showmessage_', str) = 1 then begin MID := StrToInt(Copy(str, Pos('_', str) + 1, 5)); frmRecieverMain.NotifyMessage(MID); end else if str = 'updateusers' then LoadUsers else if str = 'updatemessages' then LoadMessages; except Break; end; Sleep(2000); finally TCP1.Disconnect; TCP1.Free; end; – Farshad H. Dec 16 '12 at 05:31
  • Please update your original question with the code you and using, both client and server, and make sure to format it properly for readibility. Also, if you could, provide a link or summary about what the TCD protocol is that you are trying to implement. – Remy Lebeau Dec 16 '12 at 18:51
  • I've did it, please view last answer, and comment me if it is ok ;) – Farshad H. Dec 18 '12 at 11:12
4

Clients initiate communication. That is the definition of a client–the actor that initiates the communication. Once the connection is established though, both sides can send data. So, the clients connect to the server. The server maintains a list of all connected clients. When the server wants to send out communications it just sends the data to all connected clients.

Since clients initiate communication, it follows that, in the event of broken communication, it is the client's job to re-establish connection.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • @David Heffernan Thanks for your answer :) I'm using this method currently in my software, but I have some problems when connection loses or ... , I tried to fix these problems, but i search for some simple solutions. – Farshad H. Dec 15 '12 at 09:25
  • 4
    on connection loss the client has to reconnect, thats all – Sir Rufo Dec 15 '12 at 09:45
  • @SirRufo Yeap! I used some **Thread**s for reconnecting immediately. – Farshad H. Dec 15 '12 at 09:57
  • an example where the server initiates a TCP connection is FTP in active mode – mjn Dec 15 '12 at 11:15
  • 2
    @mjn Not really. The TCP server listens, and the TCP client initiates connection. Once a connection is established, data can flow in both directions. – David Heffernan Dec 15 '12 at 12:03
  • TCP technically does not make a difference between clients and servers, there are also peer-to-peer networks over TCP (in peer-to-peer, no dedicated server provides services). Client/server defines the server as the party which provides a service which clients can use / consume. In active FTP, it is the FTP server who initiates the TCP data connection to the client. – mjn Dec 15 '12 at 16:10
  • @mjn Active FTP is a special case where FTP Client is also Server for Data Transfer. Thats why you have to use behind Firewall/Router Passive FTP. Server is called if it waits for connection and **serve** something. TCP has nothing to do with, its just one street to go – Sir Rufo Dec 15 '12 at 16:17
  • @SirRufo I agree completely - clients can be servers, and servers can be clients at the same time. Yes, I also agree that in TCP, no party explicitly can be called 'client' just because it started the communication. – mjn Dec 15 '12 at 16:22
  • @mjn You personally can use whatever terminology you like. But the rest of world attaches clear meanings to the terms client and server for TCP. The server listens. The client initiates communication by making a connection to the server. – David Heffernan Dec 15 '12 at 16:27
  • 1
    @DavidHeffernan so would you say that talk of 'clients' and 'servers' describes a common usage pattern, but is external to the definition of TCP itself? – mjn Dec 15 '12 at 17:22
  • @mjn I don't understand the question. – David Heffernan Dec 15 '12 at 17:46
4

If you want to see working code examples where server sends data, check out Indy IdTelnet: the telnet client uses a thread to listen to server messages. There is only one socket, created by the client, but the server uses the same socket for its messages to the client, at any time.

The client starts the connection, but does not have to start a conversation by saying 'HELLO' or something like that.

Technically, the client only needs to open the socket connection, without sending any additional data. The client can remain quiet as long as he wants, even until the end of the connection.

The server has a socket connection to the client as soon as the client has connected. And over this socket, the server can send data to the client.

Of course, the client has to read from the connection socket to see the server data. This can be done in a loop in a background thread, or even in the main thread (not in a VCL application of course as it would block).

mjn
  • 36,362
  • 28
  • 176
  • 378
3

Finally, this is the code that I used to solve my problem:

// Thread at client-side
procedure FNotifRecieverThread.Execute;
var
  str: string;
  MID: Integer;
  TCP1: TIdTCPClient;
begin
  if frmRecieverMain.IdTCPClient1.Connected then
  begin
    TCP1 := TIdTCPClient.Create(nil);
    TCP1.Host := frmRecieverMain.IdTCPClient1.Host;
    TCP1.Port := frmRecieverMain.IdTCPClient1.Port;
    TCP1.ConnectTimeout := 20000;

    while True do
    begin
      try
        TCP1.Connect;

        while True do
        begin
          try
            str := '';
            TCP1.SendCmd('checkmynotif');

            TCP1.Socket.WriteLn(IntToStr(frmRecieverMain.UserID));
            str := TCP1.Socket.ReadLn;

            if Pos('showmessage_', str) = 1 then
            begin
              MID := StrToInt(Copy(str, Pos('_', str) + 1, 5));
              frmRecieverMain.NotifyMessage(MID);
            end
            else
            if str = 'updateusers' then
            begin
              LoadUsers;

              frmRecieverMain.sgMsgInbox.Invalidate;
              frmRecieverMain.sgMsgSent.Invalidate;
              frmRecieverMain.cbReceipent.Invalidate;
            end
            else
              if str = 'updatemessages' then
            begin
              LoadMessages;
              frmRecieverMain.DisplayMessages;
            end;
          except
            // be quite and try next time :D
          end;          

          Sleep(2000);
        end;
      finally
        TCP1.Disconnect;
        TCP1.Free;
      end;

      Sleep(5000);
    end;
  end;
end;

// And command handlers at server-side
procedure TfrmServer.cmhCheckMyNotifCommand(ASender: TIdCommand);
var
  UserID, i: Integer;
  str: string;
begin
  str := 'notifnotfound';
  UserID := StrToIntDef(ASender.Context.Connection.Socket.ReadLn, -1);

  for i := 0 to NotificationStack.Count - 1 do
    if NotificationStack.Notifs[i].Active and
      (NotificationStack.Notifs[i].UserID = UserID)
    then
    begin
      NotificationStack.Notifs[i].Active := False;
      str := NotificationStack.Notifs[i].NotiffText;
      Break;
    end;

  ASender.Context.Connection.Socket.WriteLn(str);
end;

// And when i want to some client notificated from server, I use some methodes like this:
procedure TfrmServer.cmhSetUserOnlineCommand(ASender: TIdCommand);
var
  UserID, i: Integer;
begin
  UserID := StrToIntDef(ASender.Context.Connection.Socket.ReadLn, -1);

  if UserID <> -1 then
  begin
    for i := 0 to OnLineUsersCount - 1 do // search for duplication...
      if OnLineUsers[i].Active and (OnLineUsers[i].UserID = UserID) then
        Exit; // duplication rejected!    

    Inc(OnLineUsersCount);
    SetLength(OnLineUsers, OnLineUsersCount);

    OnLineUsers[OnLineUsersCount - 1].UserID := UserID;
    OnLineUsers[OnLineUsersCount - 1].Context := ASender.Context;
    OnLineUsers[OnLineUsersCount - 1].Active := True;

    for i := 0 to OnLineUsersCount - 1 do      // notify all other users for refresh users list
      if OnLineUsers[i].Active and (OnLineUsers[i].UserID <> UserID) then
      begin
        Inc(NotificationStack.Count);
        SetLength(NotificationStack.Notifs, NotificationStack.Count);

        NotificationStack.Notifs[NotificationStack.Count - 1].UserID := OnLineUsers[i].UserID;
        NotificationStack.Notifs[NotificationStack.Count - 1].NotiffText := 'updateusers';
        NotificationStack.Notifs[NotificationStack.Count - 1].Active := True;
      end;
  end;
end;
Ryan M
  • 18,333
  • 31
  • 67
  • 74
Farshad H.
  • 303
  • 4
  • 16
  • Why are you using a separate `TIdTCPClient` connection? More importantly, your client is using `SendCmd()` to send the `checkmynotif` command, which waits for a response from the server before then allowing your to send over the UserID. But your server-side command handler is attempting to read the UserID before sending a response. Deadlock occurs. you need to either include the UserID as a parameter of the `checkmynotif` command, or stop using `SendCmd()` since you are not using it correctly. – Remy Lebeau Dec 18 '12 at 18:27
  • @RemyLebeau I'm using a separate `TIdTCPClient` cause I'm using a thread,I'm worry about if my main connection wants to communicate with server randomly cause some collision with my thread procedure. my main client always be free for any on-demand (user requested) procedures, but my thread is always must busy to check notifications. And about using `SendCmd()` , if you have any idea or similar sample codes, please help me to find best way for safer method. – Farshad H. Dec 19 '12 at 04:55
  • 1
    I guess you do not understand me when I said you had to design your TCP protocol to work asynchronously in this situation. Any thread of your client can write a command to the socket, using a proper inter-thread locking mechanism, like a critical section, to avoid overlapping data, but DO NOT wait for the response immediately. You will need a dedicated reading thread/timer that reads ALL inbound messages, both command responses and unsolicated notifications alike, differentiating them and forwarding them to the appropriate thread for further processing. You cannot use `SendCmd()` for that. – Remy Lebeau Dec 19 '12 at 07:41