TcpClient 在连接和写入时在线程执行时给出错误

TcpClient giving errors at thread execute at connect and write

我在与 TIdTCPServer 的笔记本电脑连接上遇到问题。问题是,它连接正常,发送命令,但是当它尝试再次发送时,出现套接字错误 10053 或 10004 或 10054。

同样的代码在其他电脑上运行正常,只是在这台笔记本电脑上出现了这个错误。

我在线程中使用连接,代码如下:

type
  TThreadCon = class(TThread)
  private
    TCPClient : TIdTCPClient;
  protected
    procedure Execute; override;
  public
    constructor Create;
    destructor Destroy; override;
  end;

procedure DJWRZORLBS(millisecs: Integer);
var
  tick    : dword;
  AnEvent : THandle;
begin
  AnEvent := CreateEvent(nil, False, False, nil);
  try
    tick := GetTickCount + dword(millisecs);
    while (millisecs > 0) and (MsgWaitForMultipleObjects(1, AnEvent, False, millisecs, QS_ALLINPUT) <> WAIT_TIMEOUT) do begin
      Application.ProcessMessages;
      if Application.Terminated then Exit;
      millisecs := tick - GetTickcount;
    end;
  finally
    CloseHandle(AnEvent);
  end;
end;

constructor TThreadCon.Create;
begin
  inherited Create(True);
  TCPClient := TIdTCPClient.Create(Nil);
  TCPClient.ReadTimeout     := 3*60000;
  TCPClient.ConnectTimeout  := 3*60000;
  TCPClient.Port            := StrToInt(PortaPS);
  TCPClient.Host            := Host;
  TCPClient.IPVersion       := Id_IPv4;
  TCPClient.UseNagle        := True;
  TCPClient.ReuseSocket     := rsOSDependent;
end;

procedure TThreadCon.Execute;
begin
  while True do
  begin
    //Sleep(2500);

    try
      if not TCPClient.Connected then
      begin
        TCPClient.Connect;

        if TCPClient.Connected then
        begin
          Attempts:= 0;
          WriteLn(Format('[%s] Connected to server. [%d]', [TimeToStr(Now), Attempts]));

          TCPClient.IOHandler.WriteLn('connect');    
          if rt = nil then rt := TReadingThread.Create(TCPClient);
        end;
      end
      else
      begin
        LastPing:= GetTickCount;

        try
          TCPClient.IOHandler.WriteLn('Ping');
        except
          on E: Exception do
          begin
            WriteLn(Format('[%s] Error while trying send ping: %s', [TimeToStr(Now), E.Message]));
          end;
        end;

        WriteLn(Format('[%s] Ping send, Last Ping [%d]', [TimeToStr(Now), GetTickCount-LastPing]));
      end;
    except
      on E: Exception do
      begin
        Inc(Attempts);

        TCPClient.Disconnect(False);
        if TCPClient.IOHandler <> nil then TCPClient.IOHandler.InputBuffer.Clear;

        WriteLn(Format('[%s] Failed to connect, error: %s [%d]', [TimeToStr(Now), E.Message, Attempts]));
      end;
    end;

    DJWRZORLBS(5000);
  end;
end;

下面是发生问题的控制台日志。它连接到服务器,然后当线程 运行 再次出现时,它应该发送 Ping 的地方开始出现问题,并且由于某些原因在某些情况下它总是显示为连接在每个线程 运行,比如 TCPClient.Connected 未连接。

这是正常运行的计算机上的正常日志:

[21:44:59] Connected to server. [0]
[21:45:04] Ping send, Last Ping [0]
[21:45:09] Ping send, Last Ping [0]

如果我关闭服务器,等待几秒钟然后重新打开,它显示如下:

[21:45:54] Failed to connect, error: Socket Error # 10054
Connection reset by peer. [1]
[21:46:01] Failed to connect, error: Socket Error # 10061
Connection refused. [2]
[21:46:08] Failed to connect, error: Socket Error # 10061
Connection refused. [3]
[21:46:14] Connected to server. [0]
[21:46:19] Ping send, Last Ping [0]

对我来说,这就是它应该如何正常工作。

这是什么原因造成的?服务器有问题?但是如果是在服务器上,为什么其他机器可以正常运行?

一些网络设置?如果是,我该如何解决?

在内部,Connected 执行读取操作,这在您的情况下不是一件好事,因为如果 Connect() 成功,您有另一个线程同时从同一个套接字读取。这两个线程将争夺对套接字的访问权限并将数据放入其 IOHandler.InputBuffer.

无论如何,Connected returns True 如果InputBuffer中有任何未读数据,即使底层套接字失败。

您的 TThreadCon 结构不是很好。我建议重组它以完全消除使用 Connected 的需要(和 DJWRZORLBS(),因为 TThreadCon 没有需要抽取的消息队列)。更好的设计是让线程循环连接直到成功,然后循环发送 ping,然后断开连接,并根据需要重复。

试试像这样的东西:

type
  TThreadCon = class(TThread)
  private
    FTermEvent: TEvent;
  protected
    procedure Execute; override;
    procedure DoTerminate; override;
    procedure TerminatedSet; override;
  public
    constructor Create; reintroduce;
    destructor Destroy; override;
  end;

constructor TThreadCon.Create;
begin
  inherited Create(True);
  FTermEvent := TEvent.Create;
end;

destructor TThreadCon.Destroy;
begin
  FTermEvent.Free;
  inherited;
end;

procedure TThreadCon.TerminatedSet;
begin
  FTermEvent.SetEvent;
end;

procedure TThreadCon.Execute;
var
  TCPClient: TIdTCPClient;
  rt: TReadingThread;
  Attempts: Integer;
begin
  TCPClient := TIdTCPClient.Create(nil);
  try
    TCPClient.ReadTimeout     := 3*60000;
    TCPClient.ConnectTimeout  := 3*60000;
    TCPClient.Port            := StrToInt(PortaPS);
    TCPClient.Host            := Host;
    TCPClient.IPVersion       := Id_IPv4;
    TCPClient.UseNagle        := True;
    TCPClient.ReuseSocket     := rsOSDependent;

    Attempts := 0;

    while not Terminated do
    begin
      if TCPClient.IOHandler <> nil then
        TCPClient.IOHandler.InputBuffer.Clear;

      try
        TCPClient.Connect;
        try
          TCPClient.IOHandler.WriteLn('connect');    
        except
          TCPClient.Disonnect(False);
          raise;
        end;
      except
        on E: Exception do
        begin
          Inc(Attempts);
          WriteLn(Format('[%s] Failed to connect, error: %s [%d]', [TimeToStr(Now), E.Message, Attempts]));
          if FTermEvent.WaitFor(2500) <> wrTimeout then Exit;
          Continue;
        end;
      end;

      Attempts := 0;
      WriteLn(Format('[%s] Connected to server.', [TimeToStr(Now)]));

      rt := TReadingThread.Create(TCPClient);
      try
        try
          while not Terminated do
          begin
            LastPing := GetTickCount;      
            TCPClient.IOHandler.WriteLn('Ping');
            WriteLn(Format('[%s] Ping send, Last Ping [%d]', [TimeToStr(Now), GetTickCount-LastPing]));
            if FTermEvent.WaitFor(5000) <> wrTimeout then Exit;
          end;
        except
          on E: Exception do
          begin
            WriteLn(Format('[%s] Error while trying to send ping: %s', [TimeToStr(Now), E.Message]));
          end;
        end;            
      finally
        rt.Terminate;
        try
          TCPClient.Disconnect(False);
        finally
          rt.WaitFor;
          rt.Free;
        end;
      end;
    end;
  finally
    TCPClient.Free;
  end;
end;

procedure TThreadCon.DoTerminate;
begin
  if FatalException <> nil then
    WriteLn(Format('[%s] Fatal Error: %s', [TimeToStr(Now), Exception(E).Message]));
  inherited;
end;