繁体   English   中英

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

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

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

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

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

我需要此功能从服务器端通知客户端。 例如,当注册用户(客户端)连接到服务器时,其他连接的用户(在其他客户端)必须从服务器向所有用户发送通知(如 Yahoo Messenger)。

我正在使用TIdCmdTCPServerTIdTCPClient组件

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

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

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

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

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

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

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

如果您想查看服务器发送数据的工作代码示例,请查看 Indy IdTelnet:telnet 客户端使用线程来侦听服务器消息。 只有一个由客户端创建的套接字,但服务器在任何时候都使用相同的套接字向客户端发送消息。

客户端开始连接,但不必通过说“你好”或类似的话来开始对话。

从技术上讲,客户端只需要打开套接字连接,而不需要发送任何额外的数据。 客户端可以根据需要保持安静,甚至可以一直保持到连接结束。

一旦客户端连接,服务器就会与客户端建立套接字连接。 通过这个套接字,服务器可以向客户端发送数据。

当然,客户端必须从连接套接字中读取才能看到服务器数据。 这可以在后台线程的循环中完成,甚至可以在主线程中完成(当然不是在 VCL 应用程序中,因为它会阻塞)。

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

// 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