【问题标题】:Delphi: Try to find open TCP ports but doesn't detect any open portDelphi:尝试查找打开的 TCP 端口,但未检测到任何打开的端口
【发布时间】:2013-05-12 00:23:57
【问题描述】:

我有两个进程必须在 PC 上的两个空闲 TCP 端口上运行。对于用户来说,这必须是一个无痛的开箱即用进程,我想自动检测空闲端口以避免冲突并将这些端口号应用于这两个进程。

为了实现这一点,我创建了一个函数(也可以在线程中运行)来检测空闲端口,但没有找到任何空闲端口。有人可以解释我的代码有什么问题吗?

编辑:“@500-error etc”提供的解决方案应用于代码。功能现在可以正常工作了。

这里是:

    uses
     winsock;

    type
     TAvailablePortArray = array of Word;

function findAvailableTCPPort( ipAddressStr : String; portStart : Word = 8080; portEnd : Word = 8084; findCount : Byte = 2 ) : TAvailablePortArray;
var
  client    : sockaddr_in;
  sock      : Integer;
  ret       : Integer;
  wsdata    : WSAData;
  dwPort    : Word;
  iFound    : Byte;
  bResult   : Boolean;
  bAllFound : Boolean;
  dns       : PHostEnt;
  status    : LongInt;


begin
 setLength( Result, 0 );
 if( portStart > portEnd ) or ( portStart = 0 ) or ( findCount = 0 ) then
  Exit;

 try
 ret := WSAStartup($0002, wsdata); //initiates use of the Winsock DLL
 except
  ret:=-1;
 end;

 if( ret <> 0 ) then
  Exit;

 dns:=getHostByName( PChar(ipAddressStr) );
 if( NOT Assigned( dns )) then
  Exit;

 bResult:=TRUE;
 try
  fillChar( client, sizeOf( client ), 0 );
  client.sin_family      := AF_INET;  //Set the protocol to use , in this case (IPv4)
  client.sin_addr.s_addr :=LongInt(PLongInt(dns^.h_addr_list^)^);
  //inet_addr(PAnsiChar(ipAddressStr));  //convert to IN_ADDR  structure
 except
  bResult:=FALSE;
 end;

 if( bResult ) then
 begin
  dwPort:=portStart;
  setLength( Result, findCount );
  bAllFound:=FALSE;
  iFound:=0;

  while( NOT bAllFound ) and ( dwPort <= portEnd ) do
  begin
   try
    client.sin_port:=htons(dwPort); //convert to TCP/IP network byte order (big-endian)
    sock:=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP );    //creates a socket
    Application.processMessages();
    status:=connect(sock,client,sizeOf(client));
    bResult:=(status <> 0); //establishes a connection to a specified socket, less than zero is NOT in use
   except
    bResult:=FALSE;
   end;

   if( sock <> 0 ) then
   begin
    closesocket(sock);
    sock:=0;
   end; 

   if( bResult ) then
   begin
    Result[iFound]:=dwPort;
    inc( iFound );
    bAllFound:=( iFound = findCount );
   end;

   inc(dwPort);
  end;
 end;

 if( NOT bAllFound ) then
  setLength( Result, 0 );

 try
  WSACleanup();
 except;
 end;
end;

调用上述函数的一些代码:

procedure TForm1.btStartClick(Sender: TObject);
begin
 addLogMsg( 'Starting service ...' );
 FPorts:=findAvailableTCPPort( '127.0.0.1' );
 FPortCount:=Length( FPorts );
 addLogMsg( 'Available ports found: '+strToInt( FPortCount ));

 if( FPortCount < 2 ) then
 begin
  addLogMsg( 'ERROR: Cannot start service(s), insufficient free ports!' );
  Exit;
 end;
 ................
 ................
 ................
end;

我做错了什么?

注意:我已经调试了代码,过程似乎没问题(它试图测试它,没有例外)。还通过与其他应用程序进行测试来验证指定的端口未在使用中。

【问题讨论】:

  • socket() 出错时返回 INVALID_SOCKET (-1),而不是 0。
  • 关于否决:有时我不理解 SO 的人,为什么要否决我的问题,为什么??????糟糕的发型?

标签: windows delphi sockets ports


【解决方案1】:

您以错误的方式解决了这个问题。您应该使用bind() 而不是connect()。如果端口已在使用中,bind() 将失败。无需尝试 connect() 到单独的 IP。例如:

uses
  winsock;

type
  TAvailablePortArray = array of Word;

function findAvailableTCPPort( const ipAddressStr : AnsiString; portStart : Word = 8080; portEnd : Word = 8084; findCount : Byte = 2 ) : TAvailablePortArray;
var
  client    : sockaddr_in;
  sock      : TSocket;
  wsdata    : WSAData;
  dwPort    : Word;
  iFound    : Byte;
  bResult   : Boolean;
  arrFound  : TAvailablePortArray;
begin
  SetLength( Result, 0 );
  if ( portStart = 0 ) or ( portStart > portEnd ) or ( findCount = 0 ) then
    Exit;

  //initiates use of the Winsock DLL
  if ( WSAStartup(MAKEWORD(2, 0), wsdata) <> 0 ) then
    Exit;

  try
    //Set the protocol to use , in this case (IPv4)
    fillChar( client, sizeOf( client ), 0 );
    client.sin_family      := AF_INET;
    client.sin_addr.s_addr := inet_addr(PAnsiChar(ipAddressStr));

    dwPort := portStart;
    SetLength( arrFound, findCount );
    try
      iFound := 0;

      repeat
        sock := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    //creates a socket
        if sock = INVALID_SOCKET then Break;

        try
          if GetQueueStatus(QS_ALLINPUT) <> 0 then
            Application.ProcessMessages();

          client.sin_port := htons(dwPort); //convert to TCP/IP network byte order (big-endian)

          if bind(sock, PSockAddr(@client)^, sizeOf(client)) = 0 then
          begin
            arrFound[iFound] := dwPort;
            Inc( iFound );
          end;
        finally
          closesocket(sock);
        end;

        Inc(dwPort);
      until ( iFound = findCount ) or ( dwPort > portEnd );
    finally
      SetLength(arrFound, iFound);
    end;
  finally
    WSACleanup();
  end;

  Result := arrFound;
end;

或者,完全忘记使用套接字。而是通过 Windows 的 TCP/UDP 表进行枚举,其中列出了正在使用的活动端口。看看GetTcpTable()GetTcp6Table()GetUdpTable()GetUdp6Table()

话虽如此,这两种方法都存在根本缺陷,因为这两种方法都有竞争条件 - 在函数找到可用端口后,其他人可能会在您的进程之前打开端口。最好的选择是简单地让每个进程bind() 自己无条件地连接到端口 0,并让操作系统在此时选择一个可用端口,然后两个进程可以在需要时宣布它们指定的端口。

【讨论】:

  • 如果我使用绑定,结果总是为零。当我检查 WSAGetLastError 时,当端口正在使用时,返回值永远不会是 WSAEADDRINUSE。所以我错过了什么吗?
  • bind() 返回零时,表示指定的端口可用,现在已分配给绑定的套接字。 WSAGetLastError() 仅在发生错误时才有意义。
  • 不是一个真实的故事。无论如何,bind 函数总是返回零。测试了一些在指定端口上运行的服务器,但始终没有相同的结果。
  • 我向您保证,bind() 无法绑定到已在使用的端口。它将报告WSAEADDRINUSE。唯一不会发生的方法是,如果您启用了 SO_REUSEADDR 选项,那么您不应该这样做。
  • 您好 Remy,测试了您的功能,但它无法正常工作。它告诉我端口 8080 是免费的,但它不是。
【解决方案2】:

我(现在)认为问题在于您误解了 connect 的结果。

如果connect成功(返回零),则表示该端口正在使用中。

【讨论】:

  • 您的解决方案不是摇滚科学,但您是对的!谢谢。未连接时,返回-1。你知道这段代码的确切含​​义吗?
  • this
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-23
  • 2012-10-17
  • 1970-01-01
  • 2011-08-18
  • 2014-09-17
  • 1970-01-01
  • 2013-04-11
相关资源
最近更新 更多