4

我想创建一个 TCPserver 并根据需要向客户端发送/接收消息,而不是 TCPserver 的 OnExecute 事件。

发送/接收消息不是问题;我喜欢这样:

procedure TFormMain.SendMessage(IP, Msg: string);
var
  I: Integer;
begin
  with TCPServer.Contexts.LockList do
  try
    for I := 0 to Count-1 do
      if TIdContext(Items[I]).Connection.Socket.Binding.PeerIP = IP then
      begin
        TIdContext(Items[I]).Connection.IOHandler.WriteBuffer(Msg[1], Length(Msg));
        //  and/or Read 
        Break;
      end;
  finally
    TCPServer.Contexts.UnlockList;
  end;
end;

注意 1:如果我不使用 OnExecute,程序会在客户端连接时引发异常。
注意 2:如果我在不做任何事情的情况下使用 OnExecute,CPU 使用率会变为 %100
注意 3:我没有机会更改 TCP 客户端。

所以我该怎么做?

4

5 回答 5

7

TIdTCPServer需要OnExecute默认分配的事件处理程序。为了解决这个问题,您必须从虚拟方法派生一个新类TIdTCPServer并覆盖其虚拟CheckOkToBeActive()方法,并且还应该覆盖DoExecute()调用的虚拟方法Sleep()。否则,只需分配一个事件处理程序并让它调用Sleep().

但是,这不是 的有效使用TIdTCPServer。更好的设计是不要直接从方法内部将出站数据写入客户端SendMessage()。这不仅容易出错(您不会从 捕获异常WriteBuffer())并在写入期间阻塞SendMessage(),而且还会序列化您的通信(客户端 2 无法接收数据,直到客户端 1 首先接收数据)。一个更有效的设计是给每个客户端自己的线程安全的出站队列,然后SendMessage()根据需要将数据放入每个客户端的队列中。然后,您可以使用该OnExecute事件检查每个客户端的队列并进行实际写入。这样,SendMessage()不再被阻塞,更不容易出错,并且客户端可以并行写入(就像它们应该的那样)。

尝试这样的事情:

uses
  ..., IdThreadSafe;

type
  TMyContext = class(TIdServerContext)
  private
    FQueue: TIdThreadSafeStringList;
    FEvent: TEvent;
  public
    constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
    destructor Destroy; override;
    procedure AddMsgToQueue(const Msg: String);
    function GetQueuedMsgs: TStrings;
  end;

constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil);
begin
  inherited;
  FQueue := TIdThreadSafeStringList.Create;
  FEvent := TEvent.Create(nil, True, False, '');
end;

destructor TMyContext.Destroy;
begin
  FQueue.Free;
  FEvent.Free;
  inherited;
end;

procedure TMyContext.AddMsgToQueue(const Msg: String);
begin
  with FQueue.Lock do
  try
    Add(Msg);
    FEvent.SetEvent;
  finally
    FQueue.Unlock;
  end;
end;

function TMyContext.GetQueuedMsgs: TStrings;
var
  List: TStringList;
begin
  Result := nil;
  if FEvent.WaitFor(1000) <> wrSignaled then Exit;
  List := FQueue.Lock;
  try
    if List.Count > 0 then
    begin
      Result := TStringList.Create;
      try
        Result.Assign(List);
        List.Clear;
      except
        Result.Free;
        raise;
      end;
    end;
    FEvent.ResetEvent;
  finally
    FQueue.Unlock;
  end;
end;

procedure TFormMain.FormCreate(Sender: TObject);
begin
  TCPServer.ContextClass := TMyContext;
end; 

procedure TFormMain.TCPServerExecute(AContext: TIdContext);
var
  List: TStrings;
  I: Integer;
begin
  List := TMyContext(AContext).GetQueuedMsgs;
  if List = nil then Exit;
  try
    for I := 0 to List.Count-1 do
      AContext.Connection.IOHandler.Write(List[I]);
  finally
    List.Free;
  end;
end;

procedure TFormMain.SendMessage(const IP, Msg: string); 
var 
  I: Integer; 
begin 
  with TCPServer.Contexts.LockList do 
  try 
    for I := 0 to Count-1 do 
    begin
      with TMyContext(Items[I]) do
      begin
        if Binding.PeerIP = IP then 
        begin 
          AddMsgToQueue(Msg); 
          Break; 
        end;
      end; 
    end;
  finally 
    TCPServer.Contexts.UnlockList; 
  end; 
end; 
于 2012-02-17T23:41:04.107 回答
4

使用 OnExecute,如果您无事可做,则 Sleep() 一段时间,比如 10 毫秒。每个连接都有自己的 OnExecute 处理程序,因此这只会影响每个单独的连接。

于 2012-02-17T08:24:08.293 回答
1

Indy组件集旨在模拟网络连接上的阻塞操作。您应该将所有代码封装在OnExecute事件处理程序中。这应该更容易,因为大多数协议都会阻止任何方式(发送命令、等待响应等)。

你显然不喜欢它的操作模式,你想要一些没有阻塞的东西。您应该考虑使用专为您打算使用它的方式而设计的组件套件:试试ICS套件!ICS 不使用线程,所有工作都在事件处理程序中完成。

于 2012-02-17T10:07:52.420 回答
1

在 OnExecute 处理程序中,您可以使用 TEvent 和 TMonitor 等线程通信方法等待客户端有数据。

TMonitor 从 Delphi 2009 开始可用,并提供方法(Wait、Pulse 和 PulseAll)以最小的 CPU 使用率发送/接收通知。

于 2012-02-17T11:48:08.537 回答
1

我有类似的情况,占用 100% CPU,它通过添加 IdThreadComponent 和解决:

void __fastcall TForm3::IdThreadComponent1Run(TIdThreadComponent *Sender)
{
    Sleep(10);
}

这样对吗?我不确定。

于 2012-05-28T03:48:13.033 回答