简体   繁体   English

首先在 Indy 10 的 delphi 中从服务器开始通信

[英]Start Communication from server first in delphi by Indy 10

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.在由 TCPServer/Client 组件编写的 Socket 应用程序中,通常我们激活服务器端,然后将客户端连接到服务器,当我们需要从一侧到另一端获取或发送数据时,首先我们从客户端向服务器发送命令,然后进行通信开始。

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).例如,当注册用户(客户端)连接到服务器时,其他连接的用户(在其他客户端)必须从服务器向所有用户发送通知(如 Yahoo Messenger)。

I'm using TIdCmdTCPServer and TIdTCPClient components我正在使用TIdCmdTCPServerTIdTCPClient组件

You are using TIdCmdTCPServer .您正在使用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.对于您所要求的,您应该改用TIdTCPServer ,然后您可以在TIdTCPServer.OnExecute事件中做任何您想做的事情。

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.需要时使用TIdContext.Connection.IOHandler You can loop through existing clients in the TIdTCPServer.Contexts list, such as inside the TIdTCPServer.OnConnect and TIdTCPServer.OnDisconnect events.您可以遍历TIdTCPServer.Contexts列表中的现有客户端,例如在TIdTCPServer.OnConnectTIdTCPServer.OnDisconnect事件中。 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.查看TIdCmdTCPClientTIdTelnet以获取相关示例。

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.您可以为此使用TIdContext.Data属性。 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.然后,您可以在需要时将服务器消息添加到队列中,并让OnExecute事件在不执行任何其他操作时定期发送队列。 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.您仍然需要客户端的计时器/线程,它需要处理对客户端命令和未经请求的服务器消息的响应,因此您不能使用TIdConnection.SendCmd()或相关方法,因为它不知道它是什么将结束阅读。

I have posted examples of both approaches in the Embarcadero and Indy forums many times before.我之前在 Embarcadero 和 Indy 论坛上多次发布了这两种方法的示例。

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.由于客户端发起通信,因此在通信中断的情况下,重新建立连接是客户端的工作。

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.如果您想查看服务器发送数据的工作代码示例,请查看 Indy IdTelnet:telnet 客户端使用线程来侦听服务器消息。 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).这可以在后台线程的循环中完成,甚至可以在主线程中完成(当然不是在 VCL 应用程序中,因为它会阻塞)。

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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM