使用代理服务器发送GET请求时复用TCP套接字遇销毁问题求助
问题分析与解决方案
核心问题点
- keepAlive超时设置过短:
keepAliveMsecs: 10意味着socket闲置10毫秒就会被Agent自动销毁,远低于正常复用的时间窗口,导致你存到sockets里的socket很快就失效了。 - socket被双重管理冲突:你通过
http.Agent创建的socket,会被Agent接管生命周期。即使你把socket存到自己的集合里,Agent依然会按照自身规则销毁闲置连接,导致复用拿到的socket已被销毁。 - 未清理失效socket:当socket因超时、错误等原因销毁时,你的
sockets集合没有同步移除失效实例,后续获取到的都是已销毁的socket。 - 请求路径错误:原代码中
path: url.href会包含完整的URL(包括host),导致目标服务器接收到错误的请求路径。 - 请求结束后socket被自动销毁:使用
https.Agent传入socket时,默认请求结束后Agent会销毁socket,无法实现复用。
修复后的代码实现
import * as http from 'http'; import * as https from 'https'; import { AxiosProxyConfig } from 'axios'; import { Socket } from 'net'; class ProxySocketManager { private sockets = new Map<string, Socket>(); private connect(urlString: string, proxy: AxiosProxyConfig): Promise<Socket> { const url = new URL(urlString); return new Promise((resolve, reject) => { // 不使用Agent,避免Agent接管socket生命周期 const req = http.request({ method: "CONNECT", host: proxy.host, path: url.host, port: proxy.port, headers: { "Connection": "Keep-Alive", ...(proxy.auth ? { "Proxy-Authorization": `Basic ${Buffer.from(`${proxy.auth.username}:${proxy.auth.password}`).toString('base64')}` } : {}) } }); req.on("connect", (_, socket) => { // 设置socket闲置超时,避免被服务器主动断开 socket.setTimeout(30000); // 监听销毁事件,及时从集合中移除失效socket socket.on('close', () => { this.sockets.delete(`${proxy.host}${proxy.port}`); }); socket.on('error', () => { this.sockets.delete(`${proxy.host}${proxy.port}`); }); this.sockets.set(`${proxy.host}${proxy.port}`, socket); resolve(socket); }); req.on("error", reject); req.end(); }); } private newRequest(urlString: string): Promise<string> { const url = new URL(urlString); const proxy = this.proxyStack.get(); const socketKey = `${proxy.host}${proxy.port}`; return new Promise(async (resolve, reject) => { let socket = this.sockets.get(socketKey); // 复用前校验socket有效性:未销毁且可写 if (!socket || socket.destroyed || !socket.writable) { socket = await this.connect(urlString, proxy); } // 创建仅复用指定socket的Agent,禁止自动销毁 const agent = new https.Agent({ socket, keepAlive: true, maxSockets: 1, maxFreeSockets: 1 }); const req = https.get({ host: url.host, path: `${url.pathname}${url.search}`, // 修正为正确的请求路径 agent, headers: { "Accept": "application/json", "Accept-Encoding": "gzip, deflate, br", "User-Agent": "axios/0.27.2", "Connection": "Keep-Alive" // 告知目标服务器保持连接 } }, (res) => { if (res.statusCode !== 200) { return reject(new Error(`HTTP请求失败,状态码:${res.statusCode}`)); } let chunks: Buffer[] = []; res.on('data', chunk => chunks.push(chunk)) .on('end', () => { resolve(Buffer.concat(chunks).toString()); }) .on("error", reject); }); req.on("error", (e) => { // 请求出错时移除失效socket,避免下次复用 this.sockets.delete(socketKey); reject(e); }); }); } // 此处为proxyStack示例实现,需替换为你实际的代理获取逻辑 private proxyStack = { get(): AxiosProxyConfig { return { host: 'proxy.example.com', port: 8080 }; } }; }
关键修复说明
- 移除http.Agent:CONNECT请求不再依赖Agent,完全手动管理socket生命周期,避免Agent自动销毁连接。
- socket状态监听:通过
close和error事件实时清理失效socket,确保集合中只保留可用连接。 - 修正请求路径:使用
url.pathname + url.search替代url.href,保证请求路径符合HTTP规范。 - https.Agent配置:限制socket数量并开启
keepAlive,确保Agent不会销毁传入的socket,而是保持复用状态。 - 有效性校验:复用前检查socket是否处于可用状态,无效则重新创建连接。
内容的提问来源于stack exchange,提问作者zzmichalqq
相关产品推荐
相关产品推荐

