Question

I have two processes that must run on two free TCP ports on the PC. This must be a painless out-of-the-box process for it's user, I want to auto detect free ports to avoid conflicts and apply these port numbers to these two processes.

To achieve this, I made a function (that runs in a thread also) to detect the free ports but doesn't find any free ports. Can somebody explain what is wrong with my code?

EDIT: The solution provided by "@500-error etc" is applied to the code. Function now working OK.

Here it is:

    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;

Some code to call the function above:

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;

What I am doing wrong?

NOTE: I have debug the code, the process seems to be OK (it tries to test it, no exceptions). Also verified that the ports specified are not in use by testing it with other app.

Was it helpful?

Solution

I (now) believe the issue is that you are misinterpreting the result from connect.

If connect succeeds (returns zero), it means that the port is in use.

OTHER TIPS

You went around this the wrong way. You should be using bind() instead of connect(). If a port is already in use, bind() will fail. No need to attempt to connect() to a separate IP. For example:

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;

Alternatively, forget using sockets at all. Enumerate through Windows' TCP/UDP tables instead, which list the active ports in use. Look at GetTcpTable(), GetTcp6Table(), GetUdpTable(), and GetUdp6Table().

With that said, either approach is fundamentally flawed, because both approaches have a race condition - after the function finds the available ports, someone else could come along and open the ports before your processes can. The best option is to simply have each process bind() itself to port 0 unconditionally and let the OS pick an available port at that moment, and then the two processes can announce their designated ports if needed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top