如何发送命令到一个单一的客户端,而不是所有的人?(How do I send a command t

2019-07-17 15:18发布

我写一个简单的客户端/服务器的聊天程序与印10.我的服务器(idtcpserver)发送命令给客户端,客户端的答案,但是当多个客户端连接和服务器发送一个命令, 所有的客户端连接将数据发送到服务器。

我怎么能发送命令到指定的客户端,而不是全部?

Answer 1:

一个命令可以被发送到所有连接的客户端的唯一方法是,如果你的代码是通过所有的客户端发送的命令,每一个循环。 所以,简单地删除该循环,或至少将其更改为只发送到特定的客户端,你是感兴趣的内容。

最好的地方将命令发送到客户端,以避免破坏与客户由于重叠指令的通信,是从客户自身的内OnExecute事件,例如:

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
begin
  ...
  if (has a command to send) then
  begin
    AContext.Connection.IOHandler.WriteLn(command here);
    ...
  end;
  ...
end;

如果您需要从其他线程发送命令给客户端,那么最好是给该客户端自身的出境命令队列,然后有客户端的OnExecute事件发送队列中时,它是安全的这样做。 在需要的时候其他线程可以把命令到队列中。

type
  TMyContext = class(TIdServerContext)
  public
    ClientName: String;
    Queue: TIdThreadSafeStringList;
    constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
    destructor Destroy; override; 
  end;

constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil);
begin
  inherited Create(AConnection, AYarn, AList);
  Queue := TIdThreadSafeStringList.Create;
end;

destructor TMyContext.Destroy;
begin
  Queue.Free;
  inherited Destroy;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  IdTCPServer1.ContextClass := TMyContext;
end;

procedure TForm1.SendCommandToClient(const ClientName, Command: String);
var
  List: TList;
  I: Ineger;
  Ctx: TMyContext;
begin
  List := IdTCPServer1.Contexts.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      Ctx := TMyContext(List[I]);
      if Ctx.ClientName = ClientName then
      begin
        Ctx.Queue.Add(Command);
        Break;
      end;
    end;
  finally
    IdTCPServer1.Context.UnlockList;
  end;
end;

procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
var
  List: TList;
  I: Ineger;
  Ctx, Ctx2: TMyContext;
  ClientName: String;
begin
  Ctx := TMyContext(AContext);
  ClientName := AContext.Connection.IOHandler.ReadLn;
  List := IdTCPServer1.Contexts.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      Ctx2 := TMyContext(List[I]);
      if (Ctx2 <> Ctx) and (Ctx.ClientName = ClientName) then
      begin
        AContext.Connection.IOHandler.WriteLn('That Name is already logged in');
        AContext.Connection.Disconnect;
        Exit;
      end;
    end;
    Ctx.ClientName = ClientName;
  finally
    IdTCPServer1.Context.UnlockList;
  end;
  AContext.Connection.IOHandler.WriteLn('Welcome ' + ClientName);
end;

procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);
var
  Ctx: TMyContext;
begin
  Ctx := TMyContext(AContext);
  Ctx.ClientName = '';
  Ctx.Queue.Clear;
end;

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
  Ctx: TMyContext;
  Queue: TStringList;
begin
  Ctx := TMyContext(AContext);
  ...
  Queue := Ctx.Queue.Lock;
  try
    while Queue.Count > 0 do
    begin
      AContext.Connection.IOHandler.WriteLn(Queue[0]);
      Queue.Delete(0);
      ...
    end;
    ...
  finally
    Ctx.Queue.Unlock;
  end;
end;


Answer 2:

通常在客户端/服务器设置,客户端发起的接触和服务器响应。 使用由IdTCPServer这暴露了事件总是特定的,所以你就不必做任何特殊情况下(连接)。

要启动从服务器到客户端的接触,你就必须跟踪连接的客户端,并使用所需的客户端的连接来发送它的消息。 要做到这一点,你需要在其中保持连接的客户端,需要实施中为onConnect和OnDisconnect事件处理程序的列表。

type
  TForm1 = class(TForm)
  private
    FClients: TThreadList;

procedure TForm1.HandleClientConnect(aThread: TIDContext);
begin
  FClients.Add(aThread);
end;

procedure TForm1.HandleClientDisconnect(aThread: TIDContext);
begin
  FClients.Remove(aThread);
end;

当你想将数据发送到特定的客户端,你可以做,使用通过TCP连接发送数据的常用方法。 但首先,你需要找到你在FClients列表中选择特定的客户端。

你将如何确定具体的客户是完全由你决定。 这完全取决于你的客户端和服务器之间交换当客户第一次连接和识别自身的信息。 说了这么多的机构将是一样的,不管这些信息。

TIDContext是使用印地保持连接的详细信息,TIdServerContext类的祖先。 您可以从TIdServerContext下降有在那里你可以存储自己的详细信息连接的地方。

type
  TMyContext = class(TIdServerContext)
  private
    // MyInterestingUserDetails...

告诉印地使用其使用自己的TIdServerContext后裔ContextClass财产。 当然,您需要激活您的服务器,例如在OnCreate之前做到这一点。

procedure TForm1.HandleTcpServerCreate(Sender: TObject);
begin
  FIdTcpServer1.ContectClass = TMyContext;
end;

然后你可以使用你自己的类无处不在,你通过它投射到自己的类有TIdContext参数:

procedure TForm1.HandleClientConnect(aThread: TIDContext);
var 
  MyContext: TMyContext;
begin
  MyContext := aThread as TMyContext;
end;

查找特定的客户端的连接,然后变成遍历您FClients清单,并检查其包含的TMyContext是否是你想要的一个的问题:

function TForm1.FindContextFor(aClientDetails: string): TMyContext;
var
  LockedList: TList;
  idx: integer;
begin
  Result := nil;
  LockedList := FClients.LockList;
  try
    for idx := 0 to LockedList.Count - 1 do
    begin
      MyContext := LockedList.Items(idx) as TMyContext;
      if SameText(MyContext.ClientDetails, aClientDetails) then
      begin
        Result := MyContext;
        Break;
      end;
    end;
  finally
    FClients.UnlockList;
  end;

编辑 :正如雷米在评论中指出:线程安全,你应该保持在写客户端(这是不是吞吐量和性能这样的好事),或在雷米的话锁定列表:

“一个更好的选择是给TMyContext自己的TIdThreadSafeStringList出站数据,然后有客户端的OnExecute事件编写清单到客户端时,它是安全的这样做。然后,其他客户的OnExecute事件可以将数据推送到该名单需要时“。



文章来源: How do I send a command to a single client instead of all of them?