forked from shiftwinting/FastGithub
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathIPAddressService.cs
More file actions
142 lines (125 loc) · 5.54 KB
/
IPAddressService.cs
File metadata and controls
142 lines (125 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.DomainResolve
{
/// <summary>
/// IP服务
/// 域名IP关系缓存10分钟
/// IPEndPoint时延缓存5分钟
/// IPEndPoint连接超时5秒
/// </summary>
sealed class IPAddressService
{
private record DomainAddress(string Domain, IPAddress Address);
private readonly TimeSpan domainAddressExpiration = TimeSpan.FromMinutes(10d);
private readonly IMemoryCache domainAddressCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
private record AddressElapsed(IPAddress Address, TimeSpan Elapsed);
private readonly TimeSpan problemElapsedExpiration = TimeSpan.FromMinutes(1d);
private readonly TimeSpan normalElapsedExpiration = TimeSpan.FromMinutes(5d);
private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(5d);
private readonly IMemoryCache addressElapsedCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
private readonly DnsClient dnsClient;
/// <summary>
/// IP服务
/// </summary>
/// <param name="dnsClient"></param>
public IPAddressService(DnsClient dnsClient)
{
this.dnsClient = dnsClient;
}
/// <summary>
/// 并行获取可连接的IP
/// </summary>
/// <param name="dnsEndPoint"></param>
/// <param name="oldAddresses"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IPAddress[]> GetAddressesAsync(DnsEndPoint dnsEndPoint, IEnumerable<IPAddress> oldAddresses, CancellationToken cancellationToken)
{
var ipEndPoints = new HashSet<IPEndPoint>();
// 历史未过期的IP节点
foreach (var address in oldAddresses)
{
var domainAddress = new DomainAddress(dnsEndPoint.Host, address);
if (this.domainAddressCache.TryGetValue(domainAddress, out _))
{
ipEndPoints.Add(new IPEndPoint(address, dnsEndPoint.Port));
}
}
// 新解析出的IP节点
await foreach (var address in this.dnsClient.ResolveAsync(dnsEndPoint, fastSort: false, cancellationToken))
{
ipEndPoints.Add(new IPEndPoint(address, dnsEndPoint.Port));
var domainAddress = new DomainAddress(dnsEndPoint.Host, address);
this.domainAddressCache.Set(domainAddress, default(object), this.domainAddressExpiration);
}
if (ipEndPoints.Count == 0)
{
return Array.Empty<IPAddress>();
}
var addressElapsedTasks = ipEndPoints.Select(item => this.GetAddressElapsedAsync(item, cancellationToken));
var addressElapseds = await Task.WhenAll(addressElapsedTasks);
return addressElapseds
.Where(item => item.Elapsed < TimeSpan.MaxValue)
.OrderBy(item => item.Elapsed)
.Select(item => item.Address)
.ToArray();
}
/// <summary>
/// 获取IP节点的时延
/// </summary>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<AddressElapsed> GetAddressElapsedAsync(IPEndPoint endPoint, CancellationToken cancellationToken)
{
if (this.addressElapsedCache.TryGetValue<AddressElapsed>(endPoint, out var addressElapsed))
{
return addressElapsed;
}
var stopWatch = Stopwatch.StartNew();
try
{
using var timeoutTokenSource = new CancellationTokenSource(this.connectTimeout);
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
using var socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(endPoint, linkedTokenSource.Token);
addressElapsed = new AddressElapsed(endPoint.Address, stopWatch.Elapsed);
return this.addressElapsedCache.Set(endPoint, addressElapsed, this.normalElapsedExpiration);
}
catch (Exception ex)
{
cancellationToken.ThrowIfCancellationRequested();
addressElapsed = new AddressElapsed(endPoint.Address, TimeSpan.MaxValue);
var expiration = IsLocalNetworkProblem(ex) ? this.problemElapsedExpiration : this.normalElapsedExpiration;
return this.addressElapsedCache.Set(endPoint, addressElapsed, expiration);
}
finally
{
stopWatch.Stop();
}
}
/// <summary>
/// 是否为本机网络问题
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
private static bool IsLocalNetworkProblem(Exception ex)
{
if (ex is not SocketException socketException)
{
return false;
}
var code = socketException.SocketErrorCode;
return code == SocketError.NetworkDown || code == SocketError.NetworkUnreachable;
}
}
}