简体   繁体   English

哪个Indy组件要使用?

[英]Which Indy components to use?

I am quite new to Indy, have implemented very basic idTCPServer and IdTCPClient between 2 pc's on local LAN (no internet), but now need 3 machines to talk! 我是Indy的新手,已在本地局域网(无互联网)上实现了2个pc之间的非常基本的idTCPServer和IdTCPClient,但现在需要3台机器进行通话! (Environment is Delphi 2010/Win7/DBExpress/MySQL). (环境是Delphi 2010 / Win7 / DBExpress / MySQL)。 Application is to control a real-time sailing event. 应用是控制实时航行事件。 A "master" pc and a "slave" (now need 2 slaves!). 一个“主”pc和一个“奴隶”(现在需要2个奴隶!)。 The slave pc's enable sailors to register their details for the event (stored into MySQL tables). slave pc使得水手能够注册他们的事件细节(存储在MySQL表中)。 The master pc controls a) when registration screen opens/closes, b) sends event details to slaves, c) send real-time race countdown/start and race elapsed times to slaves which they must display and react to (closing registration screen etc). 主控制器控制a)当注册屏幕打开/关闭时,b)向从属设备发送事件详细信息,c)将实时比赛倒计时/开始和比赛经过时间发送给他们必须显示和做出反应的对象(关闭注册屏幕等) 。 Master needs to know when new person has signed in or out to update it's racelist. Master需要知道新人何时登录或退出以更新其racelist。

Currently I implemented (my first Indy program) with IDTCPServer on master. 目前我在master上使用IDTCPServer实现了(我的第一个Indy程序)。 IdTCPClient on slave tells master when new SignIn/Out, and continually sends "time request" text msgs to server, as I dont know how to send message from a TCPServer!). 奴隶上的IdTCPClient在新的SignIn / Out时告诉master,并不断向服务器发送“time request”文本消息,因为我不知道如何从TCPServer发送消息!)。

This I think is not best way to do it, and now the club wants TWO "Sign On" slaves I need to re-visit whole thing, so I ask your advice please... 我认为这不是最好的方法,现在俱乐部想要两个“登录”的奴隶,我需要重新访问整个事情,所以我请你的意见...

Which Indy components are best to use? 哪种Indy组件最好用? Is it best to have TCPServer on "master" pc? 最好在“主”电脑上安装TCPServer吗? Should the server broadcast to the 2 slaves? 服务器应该向2个从站广播吗? and (please!) is there any example that has similar functionality that I can use as a base to help me implement? 并且(请!)是否有任何具有类似功能的示例我可以作为帮助我实现的基础? Many thanks Chris 非常感谢Chris

Using TIdTCPServer on the master and TIdTCPClient on the slaves is the right way to go. 在主TIdTCPClient上使用TIdTCPServer和在从TIdTCPClient上使用TIdTCPServer是正确的方法。

One way to send messages from the server to clients is to use the server's Threads property (Indy 9 and earlier) or Contexts property (Indy 10) to access the list of currently connected clients. 从服务器向客户端发送消息的一种方法是使用服务器的Threads属性(Indy 9及更早版本)或Contexts属性(Indy 10)来访问当前连接的客户端列表。 Each client has a TIdTCPConnection object associated with it for communicating with that client. 每个客户端都有一个TIdTCPConnection关联的TIdTCPConnection对象,用于与该客户端通信。 When needed, you can lock the server's client list, loop through it writing your message to each client, and then unlock the list: 在需要时,您可以锁定服务器的客户端列表,循环将消息写入每个客户端,然后解锁列表:

Indy 9: Indy 9:

procedure TMaster.SendMsg(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer1.Threads.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      try
        TIdPeerThread(List[I]).Connection.WriteLn(S);
      except
      end;
    end;
  finally
    IdTCPServer1.Threads.UnlockList;
  end;
end;

Indy 10: Indy 10:

procedure TMaster.SendMsg(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer1.Contexts.LockList;
  try
    for I := 0 to List.Coun-1 do
    begin
      try
        TIdContext(List[I]).Connection.IOHandler.WriteLn(S);
      except
      end;
    end;
  finally
    IdTCPServer1.Contexts.UnlockList;
  end;
end;

This has some disadvantages, though. 但这有一些缺点。

One disadvanage is that messages are serialized so if one client freezes up, subsequent clients will not receive their messages in a timely manner. 一个不利之处是消息被序列化,因此如果一个客户端冻结,后续客户端将不会及时收到消息。 Another issue is that clients run in their own threads on the server, so when sending data to a client from multiple threads at the same time, you would have to provide your own per-connection locking mechanism (such as a critical section or mutex) around every write access to a connection to avoid overlapping data and corrupting your communications 另一个问题是客户端在服务器上运行自己的线程,因此当同时从多个线程向客户端发送数据时,您必须提供自己的每个连接锁定机制(例如关键部分或互斥锁)围绕对连接的每次写访问,以避免重叠数据和破坏您的通信

To avoid those pitfalls, it is better to give each client an outbound queue of messages, and then let the server's OnExecute even send the queued messages on its own schedule. 为了避免这些陷阱,最好给每个客户端一个出站消息队列,然后让服务器的OnExecute按照自己的时间表发送排队的消息。 That way, multiple clients can receive messages in parallel instead of serially: 这样,多个客户端可以并行而不是串行接收消息:

Indy 9: Indy 9:

uses
  ..., IdThreadSafe;

procedure TMaster.IdTCPServer1Connect(AThread: TIdPeerThead);
begin
  AThread.Data := TIdThreadSafeStringList.Create;
end;

procedure TMaster.IdTCPServer1Disconnect(AThread: TIdPeerThead);
begin
  AThread.Data.Free;
  AThread.Data := nil;
end;

procedure TMaster.IdTCPServer1Execute(AThread: TIdPeerThead);
var
  Queue: TIdThreadSafeStringList;
  List: TStringList;
  Tmp: TStringList;
  I: Integer;
begin
  ...
  Queue := TIdThreadSafeStringList(AThread.Data);
  List := Queue.Lock;
  try
    if List.Count > 0 then
    begin
      Tmp := TStringList.Create;
      try
        Tmp.Assign(List);
        List.Clear;
      except
        Tmp.Free;
        raise;
      end;
    end;
  finally
    Queue.Unlock;
  end;
  if Tmp <> nil then
  try
    AThread.Connection.WriteStrings(Tmp, False);
  finally
    Tmp.Free;
  end;
  ...
end;

procedure TMaster.SendMsg(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer1.Threads.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      try
        TIdThreadSafeStringList(TIdPeerThread(List[I]).Data).Add(S);
      except
      end;
    end;
  finally
    IdTCPServer1.Threads.UnlockList;
  end;
end;

Indy 10: Indy 10:

uses
  ..., IdThreadSafe;

procedure TMaster.IdTCPServer1Connect(AContext: TIdContext);
begin
  AContext.Data := TIdThreadSafeStringList.Create;
end;

procedure TMaster.IdTCPServer1Disconnect(AContext: TIdContext);
begin
  AContext.Data.Free;
  AContext.Data := nil;
end;

procedure TMaster.IdTCPServer1Execute(AContext: TIdContext);
var
  Queue: TIdThreadSafeStringList;
  List: TStringList;
  Tmp: TStringList;
  I: Integer;
begin
  ...
  Queue := TIdThreadSafeStringList(AContext.Data);
  List := Queue.Lock;
  try
    if List.Count > 0 then
    begin
      Tmp := TStringList.Create;
      try
        Tmp.Assign(List);
        List.Clear;
      except
        Tmp.Free;
        raise;
      end;
    end;
  finally
    Queue.Unlock;
  end;
  if Tmp <> nil then
  try
    AContext.Connection.IOHandler.Write(Tmp, False);
  finally
    Tmp.Free;
  end;
  ...
end;

procedure TMaster.SendMsg(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer1.Contexts.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      try
        TIdThreadSafeStringList(TIdContext(List[I]).Data).Add(S);
      except
      end;
    end;
  finally
    IdTCPServer1.Contexts.UnlockList;
  end;
end;

Even with the queuing in place to address multithreading concerns, another disadvantage is that things still do not play very well if your server's OnExecute event or CommandHandlers collection has to send data back to a client in response to a command from the client at the same time that the server is broadcasting messages to those same clients. 即使有排队解决多线程问题,另一个缺点是,如果服务器的OnExecute事件或CommandHandlers集合必须将数据发送回客户端以响应来自客户端的命令,那么事情仍然不能很好地发挥作用服务器正在向这些客户端广播消息。 After a client sends a command and tries to read back a response, it may receive a broadcast instead, and the real response will be received after sending another command later on. 在客户端发送命令并尝试回读响应之后,它可能会接收到广播,并且稍后在发送另一个命令之后将收到实际响应。 The client would have to detect broadcasts so it can keep reading until it gets the response it is expecting. 客户端必须检测广播,以便它可以继续阅读,直到它获得预期的响应。

You are essentially asking for two separate communication models. 您实际上是在寻求两种独立的通信模型。 One model allows a client to send commands to the server (SignIn/Out, etc), and another model where the server sends real-time broadcasts to clients. 一种模型允许客户端向服务器发送命令(SignIn / Out等),以及服务器向客户端发送实时广播的另一种模型。 Trying to manage those two models over a single connection is doable, but tricky. 尝试通过单个连接管理这两个模型是可行的,但很棘手。 A simple solution would be to move the broadcasts to another TIdTCPServer that just sends broadcasts and does nothing else. 一个简单的解决方案是将广播移动到另一个仅发送广播的TIdTCPServerTIdTCPServer执行任何其他操作。 The master can have two TIdTCPServer components running, listening on different ports, and then each slave can have two TIdTCPClient components running, one for sending commands and one for receiving broadcasts. 主设备可以运行两个TIdTCPServer组件,监听不同的端口,然后每个从设备可以运行两个TIdTCPClient组件,一个用于发送命令,另一个用于接收广播。 The downside is that each slave would have to keep 2 connections to the master, which can eat up network bandwidth if you have a lot of slaves connected at one time. 缺点是每个从站必须保持与主站的2个连接,如果你一次连接了很多从站,这可能会占用网络带宽。 But it does keep your coding fairly simple on both sides. 但它确实使你的编码在双方都相当简单。

Indy 9: Indy 9:

procedure TMaster.IdTCPServer1Execute(AThread: TIdPeerThread);
var
  S: String;
begin
  S := AThread.Connection.ReadLn;
  if S = 'SignIn' then
    ...
  else if S = 'SignOut' then
    ...
  else
    ...
end;

procedure TMaster.SendBroadcast(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer2.Threads.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      try
        TIdPeerThread(List[I]).Connection.WriteLn(S);
      except
      end;
    end;
  finally
    IdTCPServer2.Threads.UnlockList;
  end;
end;

.

procedure TSlave.Connect;
begin
  IdTCPClient1.Connect;
  IdTCPClient2.Connect;
end;

procedure TSlave.SignIn;
begin
  IdTCPClient1.SendCmd('SignIn');
  ...
end;

procedure TSlave.SignOut;
begin
  IdTCPClient1.SendCmd('SignOut');
  ...
end;

procedure TSlave.IdTCPClient2Connect(Sener: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.IdTCPClient2Connect(Sener: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.Timer1Elapsed(Sender: TObject);
var
  S: String;
begin
  try
    if IdTCPClient2.InputBuffer.Size = 0 then
      IdTCPClient2.ReadFromStack(True, 0, False);
    while IdTCPClient2.InputBuffer.Size > 0 do
    begin
      S := IdTCPClient2.ReadLn;
      ... handle broadcast ...
    end;
  except
    on E: EIdException do
      IdTCPClient2.Disconnect;
  end;
end;

Indy 10: Indy 10:

procedure TMaster.IdTCPServer1Execute(AContext: TIdContext);
var
  S: String;
begin
  S := AContext.Connection.IOHandler.ReadLn;
  if S = 'SignIn' then
    ...
  else if S = 'SignOut' then
    ...
  else
    ...
end;

procedure TMaster.SendBroadcast(const S: String);
var
  List: TList;
  I: Integer;
begin
  List := IdTCPServer2.Contexts.LockList;
  try
    for I := 0 to List.Count-1 do
    begin
      try
        TIdContext(List[I]).Connection.IOHandler.WriteLn(S);
      except
      end;
    end;
  finally
    IdTCPServer2.Contexts.UnlockList;
  end;
end;

.

procedure TSlave.Connect;
begin
  IdTCPClient1.Connect;
  IdTCPClient2.Connect;
end;

procedure TSlave.SignIn;
begin
  IdTCPClient1.SendCmd('SignIn');
  ...
end;

procedure TSlave.SignOut;
begin
  IdTCPClient1.SendCmd('SignOut');
  ...
end;

procedure TSlave.IdTCPClient2Connect(Sener: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.IdTCPClient2Connect(Sener: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.Timer1Elapsed(Sender: TObject);
var
  S: String;
begin
  try
    if IdTCPClient2.IOHandler.InputBufferIsEmpty then
      IdTCPClient2.IOHandler.CheckForDataOnSource(0);
    while not IdTCPClient2.IOHandler.InputBufferIsEmpty do
    begin
      S := IdTCPClient2.IOHandler.ReadLn;
      ... handle broadcast ...
    end;
  except
    on E: EIdException do
      IdTCPClient2.Disconnect;
  end;
end;

If using separate connections for commands and broadcasts is not an option for whatever reason, then you essentially need to design your communication protocol to work asynchronously, meaning a client can send a command to the server and not wait for the response to come back right away. 如果无论出于何种原因使用单独的命令和广播连接都不是一个选项,那么您基本上需要设计通信协议以异步工作,这意味着客户端可以向服务器发送命令而不是等待响应立即返回。 The client would have to do all of its reading from inside a timer/thread, and then identify whether each received message is a broadcast or a response to a previous command and act accordingly: 客户端必须从定时器/线程内部完成所有读取,然后识别每个接收的消息是广播还是对先前命令的响应并相应地采取行动:

Indy 9: Indy 9:

procedure TSlave.IdTCPClient1Connect(Sender: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.IdTCPClient1Disconnect(Sender: TObject);
begin
  Timer1.Enabled := False;
end;

procedure TSlave.PostCmd(const S: String);
begin
  IdTCPClient1.WriteLn(S);
end;

procedure TSlave.Timer1Elapsed(Sender: TObject);
var
  S: String;
begin
  try
    if IdTCPClient1.InputBuffer.Size = 0 then
      IdTCPClient1.ReadFromStack(True, 0, False);
    while IdTCPClient1.InputBuffer.Size > 0 do
    begin
      S := IdTCPClient1.ReadLn;
      if (S is a broadcast) then
        ... handle broadcast ...
      else
        ... handle a command response ...
    end;
  except
    on E: EIdException do
      IdTCPClient1.Disconnect;
  end;
end;

Indy 10: Indy 10:

procedure TSlave.IdTCPClient1Connect(Sender: TObject);
begin
  Timer1.Enabled := True;
end;

procedure TSlave.IdTCPClient1Disconnect(Sender: TObject);
begin
  Timer1.Enabled := False;
end;

procedure TSlave.PostCmd(const S: String);
begin
  IdTCPClient1.IOHandler.WriteLn(S);
end;

procedure TSlave.Timer1Elapsed(Sender: TObject);
var
  S: String;
begin
  try
    if IdTCPClient1.IOHandler.InputBufferIsEmpty then
      IdTCPClient1.IOHandler.CheckForDataOnSource(0);
    while not IdTCPClient1.IOHandler.InputBufferIsEmpty do
    begin
      S := IdTCPClient1.IOHandler.ReadLn;
      if (S is a broadcast) then
        ... handle broadcast ...
      else
        ... handle a command response ...
    end;
  except
    on E: EIdException do
      IdTCPClient1.Disconnect;
  end;
end;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM