2

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

但问题是我们总是需要从客户端开始对话!

我想问一下在没有客户端请求的情况下从服务器端随机开始对话有什么想法吗?

我需要这个功能来从服务器端通知客户端。例如,当注册用户(客户端)连接到服务器时,其他连接的用户(在其他客户端),必须从服务器发送通知给所有用户(如雅虎通)。

我正在使用TIdCmdTCPServerTIdTCPClient组件

4

4 回答 4

8

您正在使用TIdCmdTCPServer. 根据定义,它向客户端发出的命令发送响应。对于你所要求的,你应该使用TIdTCPServer,然后你可以在事件中做任何你想做的TIdTCPServer.OnExecute事情。

您要求的是可行的,但其实施取决于您对协议的特定需求。

如果您只想发送未经请求的服务器到客户端的消息,并且从不响应客户端到服务器的命令,那么实现相当简单。需要时使用TIdContext.Connection.IOHandler。您可以循环访问列表中的现有客户端TIdTCPServer.Contexts,例如在TIdTCPServer.OnConnectTIdTCPServer.OnDisconnect事件中。在客户端,您需要一个计时器或线程来定期检查服务器消息。查看TIdCmdTCPClientTIdTelnet获取相关示例。

但是,如果您需要在同一连接上混合客户端到服务器命令和未经请求的服务器到客户端消息,则必须将协议设计为异步工作,这会使实现更加复杂。未经请求的服务器消息可以随时出现,甚至在响应客户端命令之前。命令需要包含在响应中回显的值,以便客户端可以匹配响应,并且数据包需要能够区分响应和未经请求的消息。您还必须在服务器端为每个客户端提供自己的出站队列。您可以使用该TIdContext.Data属性。然后,您可以在需要时将服务器消息添加到队列中,并让OnExecute事件在不做任何其他事情时定期发送队列。您仍然需要客户端的计时器/线程,并且它需要处理对客户端命令的响应和未经请求的服务器消息,因此您不能使用TIdConnection.SendCmd()或相关的方法,因为它不知道最终会读取什么。

我之前曾多次在 Embarcadero 和 Indy 论坛上发布过这两种方法的示例。

于 2012-12-15T18:59:13.603 回答
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).

于 2012-12-15T11:18:31.967 回答
4

客户端发起通信。这就是客户端的定义——发起通信的参与者。但是,一旦建立连接,双方就可以发送数据。因此,客户端连接到服务器。服务器维护所有已连接客户端的列表。当服务器想要发送通信时,它只是将数据发送到所有连接的客户端。

由于客户端发起通信,因此在通信中断的情况下,客户端的工作是重新建立连接。

于 2012-12-15T09:09:21.720 回答
3

最后,这是我用来解决问题的代码:

// 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;
于 2012-12-18T11:14:27.853 回答