我正在用Indy 10编写一个简单的客户端/服务器聊天程序。我的服务器(idtcpserver)向客户端发送命令,客户端应答,但是当连接了多个客户端并且服务器发送命令时,< em> all 连接的客户端将数据发送到服务器。
如何将命令发送到指定的客户端而不是全部?
答案 0 :(得分:6)
通常在客户端/服务器设置中,客户端启动联系并且服务器响应。使用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是Indy用于保存连接详细信息的TIdServerContext类的祖先。您可以从TIdServerContext下载,以便存储您自己的连接详细信息。
type
TMyContext = class(TIdServerContext)
private
// MyInterestingUserDetails...
告诉Indy使用自己的ContextClass
属性使用自己的TIdServerContext后代。您当然需要在激活服务器之前执行此操作,例如在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;
编辑:正如Remy在评论中指出的那样:对于线程安全,您应该在写入客户端时保持列表锁定(这对于吞吐量和性能来说不是一件好事),或者在Remy中词:
“更好的选择是为出站数据提供TMyContext自己的TIdThreadSafeStringList,然后让该客户端的OnExecute事件在安全的情况下将该列表写入客户端。其他客户端的OnExecute事件可以将数据推送到该客户端在需要时列出。“
答案 1 :(得分:6)
命令可以发送到所有连接的客户端的唯一方法是,如果您的代码循环通过向每个客户端发送命令的所有客户端。因此,只需删除该循环,或至少将其更改为仅发送给您感兴趣的特定客户端。
向客户端发送命令以避免因命令重叠而破坏与该客户端的通信的最佳位置来自该客户端自己的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;