You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用代理服务器发送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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 18:30:59