我們的 Delphi/Indy 應用程式在 IPv4 上使用 mDNS 發現設備,但發現的一些地址是鏈路本地 IPv6 地址(AAAA與其他A記錄一起在記錄中指定)。我們希望提供使用 IPv4 或 IPv6 地址連接到設備的選項,因為有些人堅持設定靜態 IPv4 地址,由于位置或配置的更改,這些地址與它們所在的子網不兼容。
但是,當應用程式嘗試與這些本地鏈路 IPv6 地址之一建立 TCP 連接時,第一次嘗試通常會失敗,因為 Windows 不知道要使用哪個介面。(僅從命令列 ping 地址時可以觀察到相同的行為。通過在鏈接本地地址的末尾寫入 % 后跟適當的區域 ID 來解決問題。)
因此,用于連接設備的TIdTCPClient物件需要與正確的網路介面相關聯。我知道這可以通過設定BoundIP屬性來完成,但我沒有發現設備的介面的 IPv6 地址,因為這是使用 IPv4 完成的。有沒有辦法使用在發現設備時TIdSocketHandle提供的物件OnUDPRead并使用它在物件中設定適當的東西TIdTCPClient?
uj5u.com熱心網友回復:
但是,當應用程式嘗試與這些本地鏈路 IPv6 地址之一建立 TCP 連接時,第一次嘗試通常會失敗,因為 Windows 不知道要使用哪個介面。
您是否將TIdTCPClient.IPVersion屬性設定為Id_IPv6?Indy 目前不會根據指定的 IP 地址 ( ticket ) 確定要連接的 IP 版本,您必須預先明確指定 IP 版本。
大多數時候,讓 Windows 決定在連接到 IP 地址時使用哪個介面就足夠了。這就是它的路由表的用途。
僅從命令列 ping 地址時可以觀察到相同的行為。通過在鏈接本地地址的末尾寫入 % 后跟適當的區域 ID,可以解決該問題。
Indy 目前不支持 IPv6 地址 ( ticket ) 的區域/范圍 ID。
有沒有辦法使用在發現設備時
TIdSocketHandle提供的物件OnUDPRead
不太可能,因為您說發現是通過 IPv4 進行的,這意味著TIdSocketHandle的 UDP 套接字系結到 IPv4 介面,而不是 IPv6 介面。唯一的可能性是如果有問題的介面同時分配了 IPv4 和 IPv6 地址,在這種情況下,您可以使用 Win32 API 將其與TIdSocketHandle.IP特定介面匹配,然后列舉同一介面上的 IPv6 地址。
否則,您可以使用 Indy 的TIdStack.GetLocalAddressList()方法獲取所有本地 IPv6 介面 IP,然后TIdTCPClient回圈系結到每個 IP,直到與目標設備的連接成功。
uj5u.com熱心網友回復:
接受雷米的建議,我實施了以下措施:
uses Winapi.IpTypes, IdGlobal, IdWinsock2, IdStackBSDBase;
function GetAdaptersAddresses(Family: ULONG; Flags: DWORD; Reserved: PVOID; pAdapterAddresses: PIP_ADAPTER_ADDRESSES; var OutBufLen: ULONG): DWORD; stdcall; external 'iphlpapi.dll';
// Given an IPv4 address of a network interface, this returns one of the IPv6 addresses of the same interface.
function GetAdapterIPv6Address (IPv4Address : String) : String;
const
flags = GAA_FLAG_SKIP_ANYCAST or GAA_FLAG_SKIP_MULTICAST or GAA_FLAG_SKIP_DNS_SERVER or GAA_FLAG_SKIP_FRIENDLY_NAME;
var
BufLen : ULONG;
Ret : DWORD;
Adapter, Adapters : PIP_ADAPTER_ADDRESSES;
UnicastAddr : PIP_ADAPTER_UNICAST_ADDRESS;
UnicastAddr6 : PIP_ADAPTER_UNICAST_ADDRESS;
Found : Boolean;
i : Integer;
a : UInt32;
begin
result := '';
a := IPv4ToUInt32(IPv4Address);
BufLen := 0;
Adapters := nil;
Ret := GetAdaptersAddresses(PF_UNSPEC, flags, nil, nil, BufLen);
if (Ret = ERROR_INSUFFICIENT_BUFFER) or (Ret = ERROR_BUFFER_OVERFLOW) then begin
Adapters := AllocMem(BufLen);
try
Ret := GetAdaptersAddresses(PF_UNSPEC, flags, nil, Adapters, BufLen);
if Ret = ERROR_SUCCESS then begin
Adapter := Adapters;
repeat
UnicastAddr6 := nil;
Found := false;
if (Adapter.IfType <> 24 {IF_TYPE_SOFTWARE_LOOPBACK}) and ((Adapter.Flags and IP_ADAPTER_RECEIVE_ONLY) = 0) then begin
UnicastAddr := Adapter^.FirstUnicastAddress;
while UnicastAddr <> nil do begin
if UnicastAddr^.DadState = IpDadStatePreferred then begin
case UnicastAddr^.Address.lpSockaddr.sin_family of
AF_INET : begin
with TIdIn4Addr(PSockAddrIn(UnicastAddr^.Address.lpSockaddr)^.sin_addr) do
if a = ((S_un_b.s_b1 shl 24) or (S_un_b.s_b2 shl 16) or (S_un_b.s_b3 shl 8) or S_un_b.s_b4) then
Found := true;
end;
AF_INET6 :
if UnicastAddr6 = nil then
UnicastAddr6 := UnicastAddr;
end;
end;
UnicastAddr := UnicastAddr^.Next;
end;
end;
Adapter := Adapter.Next;
until (Adapter = nil) or found;
if found and assigned(UnicastAddr6) then begin
for i := 0 to 7 do begin
if i > 0 then
result := result ':';
with TIdIn6Addr(PSockAddrIn6(UnicastAddr6^.Address.lpSockaddr)^.sin6_addr) do
result := result IntToHex(ntohs(s6_addr16[i]), 1);
end;
end;
end;
finally
FreeMem(Adapters);
end;
end;
end;
由于設備發現是通過 IPv4 完成的,網路介面的 IPv4 地址是已知的(IP屬性TIdSocketHandle),因此通過使用上述函式,TIdTCPClient.BoundIP可以設定網路介面對于在發現程序。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/496411.html
