Skip to content

Commit 47e29b9

Browse files
adaexclaude
andcommitted
feat: 精选增加 TCP 连通性测试,过滤端口不通的死节点
8 秒超时,50 并发,仅对精选分类生效。 打印连通率统计,用于评估 CI 环境的误杀率。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a7ba0a8 commit 47e29b9

1 file changed

Lines changed: 35 additions & 2 deletions

File tree

src/check.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { spawn, type ChildProcess } from 'node:child_process';
22
import fs from 'node:fs';
3+
import net from 'node:net';
34
import path from 'node:path';
45

56
import yaml from 'js-yaml';
@@ -15,6 +16,9 @@ const API_PORT = 19090;
1516
const API_BASE = `http://127.0.0.1:${API_PORT}`;
1617
const TEST_URL = 'http://www.gstatic.com/generate_204';
1718

19+
const TCP_TIMEOUT = 8000;
20+
const TCP_CONCURRENCY = 50;
21+
1822
function findMihomoBinary(): string {
1923
const candidates = [
2024
path.join(ROOT, 'mihomo'),
@@ -211,6 +215,34 @@ function extractTags(name: string): string {
211215
return `${speed}${multStr}${lossStr}`;
212216
}
213217

218+
function tcpCheck(proxy: Proxy): Promise<boolean> {
219+
return new Promise((resolve) => {
220+
const socket = net.createConnection({ host: proxy.server, port: proxy.port, timeout: TCP_TIMEOUT });
221+
socket.once('connect', () => { socket.destroy(); resolve(true); });
222+
socket.once('timeout', () => { socket.destroy(); resolve(false); });
223+
socket.once('error', () => { socket.destroy(); resolve(false); });
224+
});
225+
}
226+
227+
async function tcpFilter(proxies: Proxy[]): Promise<Proxy[]> {
228+
const alive: Proxy[] = [];
229+
let tested = 0;
230+
231+
for (let i = 0; i < proxies.length; i += TCP_CONCURRENCY) {
232+
const batch = proxies.slice(i, i + TCP_CONCURRENCY);
233+
const results = await Promise.all(batch.map(async (p) => ({ proxy: p, ok: await tcpCheck(p) })));
234+
for (const r of results) {
235+
if (r.ok) alive.push(r.proxy);
236+
}
237+
tested += batch.length;
238+
const pct = Math.round((tested / proxies.length) * 100);
239+
process.stdout.write(`\r TCP 测试: ${tested}/${proxies.length} (${pct}%) 存活: ${alive.length}`);
240+
}
241+
process.stdout.write('\n');
242+
console.log(` TCP 连通率: ${alive.length}/${proxies.length} (${Math.round((alive.length / proxies.length) * 100)}%)`);
243+
return alive;
244+
}
245+
214246
function topByCountry(proxies: Proxy[]): Proxy[] {
215247
const groups = new Map<string, Proxy[]>();
216248
for (const p of proxies) {
@@ -247,16 +279,17 @@ async function main() {
247279
{ name: '精选', file: 'curated-raw.yaml' },
248280
];
249281

250-
console.log(`\n跳过测速,仅精选 Top 筛选\n`);
282+
console.log(`\nTCP 连通性测试 + 精选 Top 筛选\n`);
251283

252284
for (const cat of categories) {
253285
const filePath = path.join(DATA_DIR, cat.file);
254286
let proxies = readYaml<{ proxies: Proxy[] }>(filePath).proxies;
255287
console.log(`${cat.name}节点 (${proxies.length}):`);
256288
if (cat.file === 'curated-raw.yaml') {
289+
proxies = await tcpFilter(proxies);
257290
const before = proxies.length;
258291
proxies = topByCountry(proxies);
259-
console.log(` Top 筛选: ${proxies.length}/${before} (HK/TW 最多 ${CURATED_LIMITS['HK']},其他 ${CURATED_DEFAULT_LIMIT})`);
292+
console.log(` Top 筛选: ${proxies.length}/${before}`);
260293
}
261294
writeYaml(filePath, { proxies });
262295
}

0 commit comments

Comments
 (0)