【问题标题】:Delphi and Indy - TCPServer OnExecute strange behaviorDelphi 和 Indy - TCPServer OnExecute 奇怪的行为
【发布时间】:2015-02-19 07:54:56
【问题描述】:

我有以下代码,“改编”自 Lebeau 在另一篇文章中的回答:Delphi XE2 / Indy TIdTCPServer / "Connection reset by peer"

type
  TClient = class(TObject)
  public
    Host: String;                 
    Queue: TIdThreadSafeStringList;
  end;

var
  Clients: TThreadList;

function TMain.HostOnTList(const Host: String): Pointer;
var
  I: Integer;
  List: TList;
begin
  Result := nil;
  List := Clients.LockList;
  try
    for I := 0 to List.Count - 1 do
      if (TClient(List[I]).Host = Host) then
      begin
        Result := List[I];
        Break;
      end;
  finally
    Clients.UnlockList;
  end;
end;

procedure TMain.FormCreate(Sender: TObject);
const
  Hosts: Array[0..4] of String = (
    'HOST1', 'HOST2', 'HOST3', 'HOST4, 'HOST5'
  );
var
  I: Integer;
  List: TList;
  Client: TClient;
begin
  Clients := TThreadList.Create;
  Clients.Duplicates := dupAccept;
  for I := Low(Hosts) to High(Hosts) do
  begin
    Client := TClient.Create;
    Client.Host := Hosts[I];
    Client.Queue := TIdThreadSafeStringList.Create;
    Clients.Add(Client);
    Client := nil;
  end;
end;

procedure TMain.FormDestroy(Sender: TObject);
var
  I: Integer;
  List: TList;
begin
  if TCPServer.Active Then
    TCPServer.Active := False;
  List := Clients.LockList;
  try
    for I := 0 to List.Count - 1 do
      TClient(List[I]).Free;
  finally
    Clients.UnlockList;
    Clients.Free;
  end;
end;

procedure TMain.TCPServerConnect(AContext: TIdContext);
var
  Host: String;  // Host String
  CIdx: Pointer; // Client Pointer
begin
  ... (get context hostname)
  CIdx := HostOnTList(Host);
  if (CIdx <> nil) then
    AContext.Data := TClient(CIdx);
  else
    ... (disconnect client)
end;

procedure TMain.TCPServerDisconnect(AContext: TIdContext);
var
  List: TList;
  Host: String;
  Client: TClient;
begin
  Host := '';
  Client := TClient(AContext.Data);
  List := Clients.LockList;
  try
    Host := Client.Host;
    if (Host <> '') then
    begin
      Client.Queue := nil;
      AContext.Data := nil;
    end;
  finally
    Clients.UnlockList;
  end;
end;

procedure TMain.idTCPServerExecute(AContext: TIdContext);
var
  I: Integer;
  List: TStringList;
begin
  Client := TClient(AContext.Data);
  ...
  List := Client.Queue.Lock;
  try
    while List.Count > 0 do
    begin
      WriteLn(List[0]);
      List.Delete(0);
    end;
  finally
    Client.Queue.Unlock;
  end;
  ...
end;

function TMain.SendMessage(const Host, Msg: String): Boolean;
var
  List: TList;
  CIdx: Pointer;
begin
  Result := False;
  CIdx := HostOnTList(Host);
  if (CIdx <> nil) then
  begin
    List := TCPServer.Contexts.LockList;
    try
      TClient(CIdx).Queue.Add(Msg);
      Result := True;
    finally
      TCPServer.Contexts.UnlockList;
    end;
  end;
end;

但是发生了一个奇怪的行为......客户端可以连接,但是一旦断开并尝试再次连接,它就被断开了。

我试图注释代码行直到找到问题所在,它发生在这一行:“List := Client.Queue.Lock;”在 idTCPServerExecute 过程中。

请问有谁知道怎么回事?

谢谢!

【问题讨论】:

  • 您的OnConnect 处理程序是否分配AContext.Data := Client;?您没有显示这一点,但您的 OnExecute 处理程序正在期待它(我在链接讨论中提供的代码完成了该任务)。你有一个OnDisconnect 处理程序来释放TClientQueue 对象吗?话虽如此,我建议从TIdServerContext 派生TClient 并将其分配给服务器的ContextClass 属性,并完全摆脱Clients 列表。服务器有自己的公共Contexts 列表,用于跟踪连接的客户端。
  • 我看到您仍在使用自己的 Clients 列表,而不是像我在您链接到的其他讨论中建议的那样消除它。我强烈建议你删除自己的列表,因为你只是在复制 TIdTCPServer 已经为你做的事情。
  • @RemyLebeau,实际上我的代码有些不同,因为我需要一个填充的 tclients 列表,并更改它们的状态 OnConnect 和 OnDisconnect 事件。像这样stackoverflow.com/questions/27555342/…。但我在连接时分配 AContext.Data := Client ,在断开连接时分配 AContext.Data := nil 。我将更新我的问题中的代码。谢谢!

标签: multithreading delphi tcp indy


【解决方案1】:

您在启动时预分配TClient 对象,并在它们连接时将它们与客户端匹配。问题是您的OnDisconnect 代码将TClient.Queue 成员设置为nil(实际上没有释放Queue 对象,从而泄漏它)但将TClient 对象留在列表中。如果客户端重新连接,OnExecute 事件在尝试访问 now-nil Queue 时崩溃。

如果您真的想重用 TClient 对象,请将您的 FormDestroyOnDisconnect 事件改为:

procedure TMain.FormDestroy(Sender: TObject);
var
  I: Integer;
  List: TList;
  Client: TClient;
begin
  if TCPServer.Active Then
    TCPServer.Active := False;
  List := Clients.LockList;
  try
    for I := 0 to List.Count - 1 do
    begin
      Client := TClient(List[I]);
      Client.Queue.Free;
      Client.Free;
    end;
  finally
    Clients.UnlockList;
    Clients.Free;
  end;
end;

procedure TMain.TCPServerDisconnect(AContext: TIdContext);
var
  Client: TClient;
begin
  Client := TClient(AContext.Data);
  if Client <> nil then
  begin
    Client.Queue.Clear;
    AContext.Data := nil;
  end;
end;

【讨论】:

  • 再次感谢 Lebeau 先生!我希望这个话题可以帮助其他有同样问题的人。
猜你喜欢
  • 2014-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-10
  • 1970-01-01
  • 1970-01-01
  • 2014-07-03
相关资源
最近更新 更多