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.
質問
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.
解決
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.
他のヒント
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.