Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I send a command to a single client instead of all of them?

I am writing a simple client/server chat program with Indy 10. My server (idtcpserver) sends a command to the client, and the client answers, but when more than one client is connected and the server sends a command, all the clients connected send data to the server.

How I can send a command to a specified client and not all?

like image 553
user1931849 Avatar asked Dec 27 '12 10:12

user1931849


1 Answers

The only way a command could be sent to all connected clients is if your code is looping through all of the clients sending the command to each one. So simply remove that loop, or at least change it to only send to the specific client that you are interested in.

The best place to send a command to a client, to avoid corrupting the communications with that client due to overlapping commands, is from within that client's own OnExecute event, eg:

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

If you need to send commands to a client from other threads, then it is best to give that client its own queue of outbound commands and then have that client's OnExecute event send the queue when it is safe to do so. Other threads can push commands into the queue when needed.

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;
like image 50
Remy Lebeau Avatar answered Nov 15 '22 03:11

Remy Lebeau