Delphi XE2 / Indy TIdTCPServer /"连接由同行重置"

时间:2014-04-14 21:41:41

标签: delphi delphi-xe2 indy tcpserver

我在Delphi XE2中使用Indy使用TIdTCPServer发送TCP消息时遇到一个问题。

例如: 我有2台设备,我将与设备1进行通信。 当我向设备1发送消息时,消息被发送正常。 但是在没有关闭程序的情况下,当我向设备2发送消息时,Delphi返回"连接由同行重置"。

以下是我的代码:

procedure TMainHost.idTCPServerNewConnect(AContext: TIdContext);
var
  Client: TSimpleClient;
begin
  Sleep(1000);
  Client := TSimpleClient.Create();

  Client.DNS := AContext.Connection.Socket.Host;
  Client.Conectado := True;
  Client.Port := idTCPServerNew.DefaultPort;
  Client.Name := 'Central';
  Client.ListLink := Clients.Count;
  Client.Thread := AContext;
  Client.IP := AContext.Connection.Socket.Binding.PeerIP;

  AContext.Data := Client;

  Clients.Add(Client);
  Sleep(500);

  if (MainEstrutura.current_central.IP = Client.IP) then
  begin
    MainEstrutura.current_central.Conectado := true;
    MainEstrutura.envia_configuracao;
  end;

end;

procedure TMainHost.idTCPServerNewDisconnect(AContext: TIdContext);
var
  Client: TSimpleClient;
begin
  { Retrieve Client Record from Data pointer }
  Client := Pointer(AContext.Data);
  { Remove Client from the Clients TList }
  Clients.Remove(Client);
  { Free the Client object }
  FreeAndNil(Client);
  AContext.Data := nil;

end;

将消息发送到设备:

procedure TMainHost.DirectTCPMessage(IP: String; TheMessage: String);
var
  Client: TSimpleClient;
  i: Integer;
  List: TList;
  Msg: String;
begin

  Msg := Trim(TheMessage);

  for i := 0 to Clients.Count - 1 do
  begin

    Client := TSimpleClient(Clients.Items[i]);

    if TIdContext(Client.Thread).Connection.Socket.Binding.PeerIP = IP then
    begin

      TIdContext(Client.Thread).Connection.Socket.WriteLn(Msg);

    end;

  end;
end;

我有另一个问题。

当我在tidtcpserver组件上设置active:= False时,应用程序崩溃。 谢谢!

1 个答案:

答案 0 :(得分:4)

您的Clients列表未受多线程访问保护。 TIdTCPServer是一个多线程组件,每个客户端都在自己的工作线程中运行。你需要考虑到这一点。我建议您完全删除Clients列表并使用TIdTCPServer.Contexts属性。否则,您需要保护Clients列表,例如将其更改为TThreadList,或者至少使用TCriticalSectionTThreadList内部Client.DNS包裹它)。

我看到的另一个问题是,您将Client.DNS字段设置为错误值,这可能会影响您的通信,具体取决于您使用procedure TMainHost.idTCPServerNewConnect(AContext: TIdContext); var Client: TSimpleClient; begin Client := TSimpleClient.Create(); Client.IP := AContext.Binding.PeerIP; Client.DNS := GStack.HostByAddress(Client.IP, AContext.Binding.IPVersion); Client.Conectado := True; Client.Port := AContext.Binding.Port; Client.Name := 'Central'; Client.Thread := AContext; AContext.Data := Client; // this may or may not need to be Synchronized, depending on what it actually does... if (MainEstrutura.current_central.IP = Client.IP) then begin MainEstrutura.current_central.Conectado := true; MainEstrutura.envia_configuracao; end; end; procedure TMainHost.idTCPServerNewDisconnect(AContext: TIdContext); var Client: TSimpleClient; begin { Retrieve Client Record from Data pointer } Client := TSimpleClient(AContext.Data); { Free the Client object } FreeAndNil(Client); AContext.Data := nil; end; 的内容。

请改为尝试:

procedure TMainHost.DirectTCPMessage(IP: String; TheMessage: String);
var
  List: TIdContextList; // or TList in an earlier version that did not have TIdContextList yet
  Context: TIdContext;
  i: Integer;
  Msg: String;
begin
  Msg := Trim(TheMessage);

  List := idTCPServerNew.Contexts.LockList;
  try
    for i := 0 to List.Count - 1 do
    begin
      Context := Context(List[i]);
      if TSimpleClient(Context.Data).IP = IP then
      begin
        try
          Context.Connection.IOHandler.WriteLn(Msg);
        except
        end;
        Break;
      end;
    end;
  finally
    idTCPServerNew.Contexts.UnlockList;
  end;
end;

OnExecute

话虽如此,如果您的服务器从CommandsHandlers事件或OnExecute集合内部发送任何数据,那么这种从其线程外部向客户端发送消息的方法并不安全,如您冒着重叠数据的风险,这会破坏与该客户的通信。更安全的方法是对传出数据进行排队,让procedure TMainHost.idTCPServerNewConnect(AContext: TIdContext); var Client: TSimpleClient; begin Client := TSimpleClient.Create(); ... Client.Queue := TIdThreadSafeStringList.Create; // <-- add this ... end; procedure TMainHost.idTCPServerNewExecute(AContext: TIdContext); var List: TStringList; I: Integer; begin Client := TSimpleClient(AContext.Data); ... List := Client.Queue.Lock; try while List.Count > 0 do begin AContext.Connection.IOHandler.WriteLn(List[0]); List.Delete(0); end; finally Client.Queue.Unlock; end; ... end; 事件在安全的情况下发送数据,例如:

procedure TMainHost.DirectTCPMessage(IP: String; TheMessage: String);
var
  List: TIdContextList; // or TList in an earlier version that did not have TIdContextList yet
  Context: TIdContext;
  i: Integer;
  Msg: String;
begin
  Msg := Trim(TheMessage);

  List := idTCPServerNew.Contexts.LockList;
  try
    for i := 0 to List.Count - 1 do
    begin
      Context := Context(List[i]);
      if TSimpleClient(Context.Data).IP = IP then
      begin
        TSimpleClient(Context.Data).Queue.Add(Msg);
        Break;
      end;
    end;
  finally
    idTCPServerNew.Contexts.UnlockList;
  end;
end;

TSimpleClient

更新:如果说,我建议从TIdServerContext派生ContextsClass并将其分配给服务器的TIdContext.Data属性,那么您不需要再次使用type TSimpleClient = class(TIdServerContext) public Queue: TIdThreadSafeStringList; ... // or TThreadList in an earlier version that did not have TIdContextThreadList yet constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override; destructor Destroy; override; end; constructor TSimpleClient.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); begin inherited; Queue := TIdThreadSafeStringList.Create; ... end; destructor TSimpleClient.Destroy; begin ... Queue.Free; inherited; end; procedure TMainHost.FormCreate(Sener: TObject); begin // this must be assigned before the server is activated idTCPServerNew.ContextClass := TSimpleClient; end; procedure TMainHost.idTCPServerNewConnect(AContext: TIdContext); var Client: TSimpleClient; ... begin Client := AContext as TSimpleClient; // use Client as needed... end; procedure TMainHost.idTCPServerNewExecute(AContext: TIdContext); var Client: TSimpleClient; ... begin Client := AContext as TSimpleClient; // use Client as needed... end; procedure TMainHost.DirectTCPMessage(IP: String; TheMessage: String); var List: TIdContextList; // or TList in an earlier version that did not have TIdContextList yet Client: TSimpleClient; i: Integer; Msg: String; begin Msg := Trim(TheMessage); List := idTCPServerNew.Contexts.LockList; try for i := 0 to List.Count - 1 do begin Client := TIdContext(Context(List[i])) as TSimpleClient; if Client.IP = IP then begin Client.Queue.Add(Msg); Break; end; end; finally idTCPServerNew.Contexts.UnlockList; end; end; 属性:

{{1}}