Indy TIdTcpServer 输入缓冲区为空

Indy TIdTcpServer input buffer empty

我有一个侦听 TCP 客户端连接的应用程序。当客户端连接时,客户端会发送一大块数据,在一个示例中为 100+k。 Indy TCPServer 接收它,processes/reformats 数据,将其发送到云端的 http 服务器,接收响应,根据响应创建确认,并将其发送回客户端,断开连接。

procedure TMyApp.TCPConnect(AContext: TIdContext);
begin
  try
    AContext.Connection.Socket.ReadTimeout := 10000;
  except
    on E:Exception do
      AContext.Connection.Disconnect;
  end;
end;

procedure TMyApp.TCPDisconnect(AContext: TIdContext);
begin
  SetLength(FMDMStr,0);
end;

function TMyApp.TransferTCPBytesToString(AnIOHandler: TIdIOHandler): String;
begin
  if AnIOHandler.InputBufferIsEmpty then
    Exit;
  Result := AnIOHandler.WaitFor(#28#13,True,True);
  if Pos(#28#13,Result) = 0 then
    Result := Result + #28#13;
end;

procedure TMyApp.WriteTCPResponse(AnIOHandler: TIdIOHandler);
var
  bytes: TIdBytes;
begin
  BuildACK(FMDMStr,bytes);
  AnIOHandler.WriteDirect(bytes, Length(bytes));
end;

procedure TMyApp.TCPExecute(AContext: TIdContext);
begin
  try
    FMDMStr := TransferTCPBytesToString(AContext.Connection.IOHandler);
    // test string for HL/7 terminating characters
    if Pos(#28#13, FMDMStr) > 0 then
    begin
      FormatAndSendHTTPMsg(FMDMStr);
      // send the response back to tcp client
      WriteTCPResponse(AContext.Connection.IOHandler);
    end;
  except
    on E:Exception do
      AContext.Connection.Disconnect;
  end;
end;

我已经在不同的场景中使用这个应用程序 2 年多了,它运行良好。我已经在多个 windows 10 & windows 服务器 2016 SE 服务器上对其进行了测试,它运行良好。

最近的部署根本不起作用;同一个客户端发送超过 100k 的数据,连接到 TIdTcpServer; execute 方法触发,但有一个 -0 大小的输入缓冲区。为了安全起见,我已将接收缓冲区设置为 256k。这是我正在记录的内容:

AContext.Connection.IOHandler.InputBufferAsString
AContext.Connection.IOHandler.RecvBufferSize
AContext.Connection.IOHandler.InputBufferIsEmpty

ReceiveBuffer 记录为 262144。InputBufferIsEmpty 总是返回 true!当然还有 InputBufferAsString := ''.

所以我想知道这是否可能是应用 运行 上的 server/network/domain 上的安全“功能”?我已经在域中的两台不同服务器上对其进行了测试,结果完全相同。客户端连接,记录它正在发送,IdTcpServer 记录连接,但什么也没收到。

如有任何想法或建议,我们将不胜感激! TIA

首先,默认 Windows Indy 套接字缓冲区大小为 32 KB。 所以,当你想发送所有东西时(我不喜欢)让你发送的数据包更小。因此,您也可以更快地检查您的连接是否存在超时或其他问题。 顺便问一下:您使用哪个版本的 Indy - 版本 9 还是版本 10?

其次,检查您的 server/client 代码,其中您 read/write “请求”和“响应”存在 - 当您处于文本模式时,读取功能应以 \r\n 结尾。

第三,检查你是否有非阻塞或阻塞服务器代码。 这意味着 - 您应该为每个连接安装一个 TThread 以正确处理所有连接(可能与其他属性一起使用)。因此,连接不会与现有的先前连接重叠。

第四(针对高级开发人员):您应该考虑为每个连接建立一个“第二”连接 - 这有助于保持线路(跳...

第五:(针对高级开发人员):您应该考虑安全方面的问题。这意味着,您应该为您的连接使用 SSL 证书(Indy 为此提供了简单的单击和编辑组件。 您的任务就是创建“自签名证书”(PuttyGen(在 Windows 上)或在 Linux 上 - 详情请参阅 google) - 如果您拥有 public证书,您不需要此步骤 - 查看“让我们加密”(一个 public 免费的 SSL 授权机构,但请注意:您必须每三个月更新一次开发者 SSL 证书(certbot 脚本可帮助您做所有这些事情)。

如果您有任何问题,请随时提出,我会尽力帮助您。

圣诞节快乐

TCPConnect()中的try..except没用。设置 ReadTimeout 不会引发异常。在任何情况下,如果未捕获的异常逃脱了 OnConnectOnExecute 事件,服务器将自动断开客户端连接。

FMDMStr 未以线程安全方式使用。每个 TIdContext 都在自己的线程中运行。不要在没有适当同步的情况下跨线程边界共享变量。在这种情况下,如果只有 1 个客户端连接(并且您通过设置 TIdTCPServer.MaxConnections=1 强制执行),那么就这样吧。否则,FMDMStr 根本不应该是 class 成员变量,它应该是 TCPExecute() 的局部变量(或者存储在 TIdContext 中),这样每个连接的客户端都在自己独特的 string.

上运行

TransferTCPBytesToString() 中的 InputBufferIsEmpty 签入需要删除。 Indy 不会填充 InputBuffer 直到它被告知执行读取操作,其中需要从底层套接字中提取新字节。所以 InputBuffer 将保持为空,直到有东西试图从连接中读取。如果您总是首先检查空的 InputBuffer,您可能会失去读取数据的机会。让WaitFor()正常阻塞直到数据到达,或者超时。

此外,由于您正在设置 AInclusive=True,因此 WaitFor() 的 return 值将始终在末尾包含 #28#13,因此 Pos() 检查是无用的,应该被删除。此外,由于 TransferTCPBytesToString() 始终 return 是一个末尾带有 #28#13 的字符串,因此 TCPExecute() 中的 Pos() 签入也是无用的。

WriteTCPResponse() 中,您根本不应该使用 TIdIOHandler.WriteDirect()。请改用适当的 TIdIOHandler.Write() 重载。发送 TIdBytes.

过载

话虽如此,试试这样的东西:

procedure TMyApp.TCPConnect(AContext: TIdContext);
begin
  AContext.Connection.IOHandler.ReadTimeout := 10000;
end;

function TMyApp.TransferTCPBytesToString(AnIOHandler: TIdIOHandler): String;
begin
  // will throw an exception on timeout...
  Result := AnIOHandler.WaitFor(#28#13,True,True);

  // alternatively:
  // Result := AnIOHandler.WaitFor(#28#13,True,True,nil,10000);
  // then you don't need to set the IOHandler.ReadTimeout...
end;

procedure TMyApp.WriteTCPResponse(AnIOHandler: TIdIOHandler; const AMsg: string);
var
  bytes: TIdBytes;
begin
  BuildACK(AMsg, bytes);
  AnIOHandler.Write(bytes);
end;

procedure TMyApp.TCPExecute(AContext: TIdContext);
var
  FMDMStr: string;
begin
  FMDMStr := TransferTCPBytesToString(AContext.Connection.IOHandler);
  // TODO: change FormatAndSendHTTPMsg() to return the response string
  // directly, rather than save it in a class member variable...
  FMDMStr := FormatAndSendHTTPMsg(FMDMStr);
  // send the response back to tcp client
  WriteTCPResponse(AContext.Connection.IOHandler, FMDMStr);
end;

仅供参考,在旁注中,您的代码提到了 HL/7。 Indy 有一个 TIdHL7 组件可以在服务器模式下运行,运行 一个内部 TIdTCPServer 读入并响应 HL/7 消息,触发一个 OnReceiveMessage 事件每条消息。在您的场景中,您可以将 TIdHL7.Port 设置为所需的侦听端口,设置 TIdHL7.CommunicationMode=cmSynchronous,设置 TIdHL7.isListener=True,将处理程序分配给 TIdHL7.OnReceiveMessage 以发送您的 HTTP 消息,然后调用TIdHL7.Start() 在运行时就绪。

只是需要考虑的事情...