我有两个进程必须在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;
我做错了什么?
注意:我已经调试了代码,过程似乎没问题(它试图测试它,没有例外)。还通过使用其他应用程序测试,验证指定的端口未使用。
答案 0 :(得分:3)
我(现在)认为问题是你误解了connect
的结果。
如果connect
成功(返回零),则表示该端口正在使用中。
答案 1 :(得分:2)
你走错了方向。您应该使用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,让操作系统在那一刻选择一个可用的端口,然后这两个进程可以根据需要宣布它们的指定端口。