首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何将命令发送到单个客户端而不是所有客户端?

如何将命令发送到单个客户端而不是所有客户端?
EN

Stack Overflow用户
提问于 2012-12-27 10:41:51
回答 2查看 2.6K关注 0票数 5

我正在用Indy 10编写一个简单的客户机/服务器聊天程序。我的服务器(idtcpserver)向客户端发送一个命令,客户端回答,但是当多个客户端连接并且服务器发送一个命令时,所有连接到服务器的客户端都向服务器发送数据。

我如何向指定的客户端发送命令,而不是全部?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2012-12-27 21:28:36

向所有连接的客户端发送命令的唯一方法是,如果您的代码循环遍历所有向每个客户端发送命令的客户端。因此,只需删除该循环,或至少将其更改为只发送到您感兴趣的特定客户端。

向客户端发送命令的最佳位置,以避免由于重叠的命令而破坏与客户端的通信,最好是从客户端自己的OnExecute事件中发送命令,例如:

代码语言:javascript
复制
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
begin
  ...
  if (has a command to send) then
  begin
    AContext.Connection.IOHandler.WriteLn(command here);
    ...
  end;
  ...
end;

如果需要从其他线程向客户端发送命令,那么最好给客户机自己的出站命令队列,然后让客户机的OnExecute事件在安全时发送队列。其他线程可以在需要时将命令推入队列。

代码语言:javascript
复制
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;
票数 6
EN

Stack Overflow用户

发布于 2012-12-27 12:27:44

通常,在客户机/服务器设置中,客户端启动联系人,服务器响应。使用IdTCPServer公开的事件,这始终是特定于上下文(连接)的,因此您不必做任何特殊的事情。

要启动从服务器到客户端的联系,您必须跟踪已连接的客户端,并使用所需客户端的连接向其发送消息。要做到这一点,您需要一个列表来保存连接的客户端,并需要为OnConnect和OnDisconnect事件实现处理程序。

代码语言:javascript
复制
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是Indy用于保存连接详细信息的TIdServerContext类的祖先。您可以从TIdServerContext下降到有一个地方,您可以存储您自己的连接详细信息。

代码语言:javascript
复制
type
  TMyContext = class(TIdServerContext)
  private
    // MyInterestingUserDetails...

告诉Indy使用您自己的TIdServerContext后代使用它的ContextClass属性。当然,在激活服务器之前,您需要这样做,例如在OnCreate中。

代码语言:javascript
复制
procedure TForm1.HandleTcpServerCreate(Sender: TObject);
begin
  FIdTcpServer1.ContectClass = TMyContext;
end;

然后,您可以在任何有TIdContext参数的地方使用您自己的类,方法是将它转换为您自己的类:

代码语言:javascript
复制
procedure TForm1.HandleClientConnect(aThread: TIDContext);
var 
  MyContext: TMyContext;
begin
  MyContext := aThread as TMyContext;
end;

然后,找到特定客户端的连接就需要迭代FClients列表,并检查它包含的TMyContext是否是您想要的:

代码语言:javascript
复制
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事件可以在需要时将数据推入该列表。”

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14053329

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档