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

时间:2012-12-15 08:49:58

标签: delphi tcp client-server indy tthread

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

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

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

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

我正在使用 TIdCmdTCPServer TIdTCPClient 组件

4 个答案:

答案 0 :(得分:8)

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

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

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

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

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

答案 1 :(得分:4)

客户启动沟通。这是客户端的定义 - 发起通信的角色。一旦建立连接,双方都可以发送数据。因此,客户端连接到服务器。服务器维护所有连接客户端的列表。当服务器想要发送通信时,它只是将数据发送到所有连接的客户端。

由于客户启动通信,因此,如果通信中断,重新建立连接是客户的工作。

答案 2 :(得分:4)

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

客户端启动连接,但不必通过说“HELLO”或类似内容来开始对话。

从技术上讲,客户端只需要打开套接字连接,而不需要发送任何其他数据。只要他愿意,客户端就可以保持安静,甚至直到连接结束。

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

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

答案 3 :(得分: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;

我希望这段代码对观众有用:)

如果您发现这些代码有用,请立即投票。