来自node.js,我可以这样做来告诉node.js 使用 ipv6 与 ipv4 发出请求
var http = require("http");
var options = {
hostname: "google.com",
family: 4, // set to 6 for ipv6
};
var req = http.request(options, function(res) {
.. handle result here ..
});
req.write("");
req.end();
将
family
设置为 4
会强制使用 ipv4,将其设置为 6
会强制使用 ipv6。不设置它就可以工作。
如何在 C# (.NET 3.5) 中做同样的事情
我可以想到一种方法,就是自己向 A 或 AAAA 记录发出 DNS 请求,发出直接 IP 请求并设置
host:
标头。有更好的办法吗?
您可以使用 ServicePoint.BindIPEndPointDelegate。
var req = HttpWebRequest.Create(url) as HttpWebRequest;
req.ServicePoint.BindIPEndPointDelegate = (servicePoint, remoteEndPoint, retryCount) =>
{
if (remoteEndPoint.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
{
return new IPEndPoint(IPAddress.IPv6Any, 0);
}
throw new InvalidOperationException("no IPv6 address");
};
几年后,.NET 5 的答案:
假设您使用的是
HttpClient
,那么您可以在 ConnectCallback
上设置 SocketsHttpHandler
,从而:
private static readonly HttpClient _http = new HttpClient(new SocketsHttpHandler() {
ConnectCallback = async (context, cancellationToken) => {
// Use DNS to look up the IP address(es) of the target host
IPHostEntry ipHostEntry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host);
// Filter for IPv4 addresses only
IPAddress ipAddress = ipHostEntry
.AddressList
.FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork);
// Fail the connection if there aren't any IPV4 addresses
if (ipAddress == null) {
throw new Exception($"No IP4 address for {context.DnsEndPoint.Host}");
}
// Open the connection to the target host/port
TcpClient tcp = new();
await tcp.ConnectAsync(ipAddress, context.DnsEndPoint.Port, cancellationToken);
// Return the NetworkStream to the caller
return tcp.GetStream();
}),
});
(此设置仅适用于 IPv4,若要仅设置 IPv6,请将 AddressFamiliy.InterNetwork 更改为 AddressFamily.InterNetworkV6)
这是我基于 @Moose Morals 的解决方案,对缓存 IP 进行了一些优化,这几乎没有什么增益(在我的专业 PC 上为 1 毫秒,但在你的 PC 上可能会更高)
(仅当主机改变时才搜索IP)
我缩短了一点,但效果是一样的
public class ResolveDnsOptimization
{
public static void ApplyTo(SocketsHttpHandler handler)
{
CachedAddress cachedAddress = null;
// Remove the latencies when using host name over IP address
// Changing pool connection lifetime and forcing to open them all does not work, the DNS resolution is always done.
// Source: https://stackoverflow.com/a/70475741/1529139
handler.ConnectCallback = async (context, cancellationToken) =>
{
if (cachedAddress == null || cachedAddress.Host != context.DnsEndPoint.Host)
{
// Use DNS to look up the IP address(es) of the target host and filter for IPv4 addresses only
IPHostEntry ipHostEntry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host);
IPAddress ipAddress = ipHostEntry.AddressList.FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork);
if (ipAddress == null)
{
cachedAddress = null;
throw new Exception($"No IP4 address for {context.DnsEndPoint.Host}");
}
cachedAddress = new CachedAddress() { Ip = ipAddress, Host = context.DnsEndPoint.Host };
}
TcpClient tcp = new();
await tcp.ConnectAsync(cachedAddress.Ip, context.DnsEndPoint.Port, cancellationToken);
return tcp.GetStream();
};
}
private class CachedAddress
{
public IPAddress Ip;
public string Host;
}
}
用途:
SocketsHttpHandler handler = new SocketsHttpHandler();
ResolveDnsOptimization.ApplyTo(handler); // <- here
HttpClient client = new HttpClient(handler)
上述两种选择
Dns.GetHostAddresses(hostname, System.Net.Sockets.AddressFamily.InterNetwork)
返回 IPv4 地址数组(对 IPv6 使用 InterNetwork6)并使用 url 中的其中一个地址。选项 1 确实有效(可能需要重新加载或重建应用程序才能使其正常工作...检查 .in\dubug
我在httpclient本身没有发现任何东西;因此,如果您只想影响特定的 httpclient 调用,那么其他答案更合适。