开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 1145|回复: 86
收起左侧

[其它源码] 【源码】go开发的全网代理IP扫描,高性能,高并发。

[复制链接]

结帖率:96% (87/91)
发表于 3 天前 | 显示全部楼层 |阅读模式   湖北省十堰市
分享源码
界面截图: -
是否带模块: 纯源码
备注说明: -
本帖最后由 花老板 于 2026-6-26 12:17 编辑

1.png


ProxyTool:从 Python 到 Go,一个代理工具的两次重生







一、起点:为什么我要重写它

事情很简单——我维护着一个从 GitHub 公开列表采集代理、自动验证、桌面监控的 Python 工具。最早用 Tkinter 糊的,跑起来能用,但当代理池膨胀到上千条的时候,问题来了。

GIL。200 个线程就跑不动了。`ThreadPoolExecutor` 的 `as_completed` 回调堆积在 UI 线程,15 秒一轮采集,窗口直接卡成幻灯片。CPU 其实很闲——代理验证本质是网络 IO 密集——但 Python 的线程模型让"闲"变成了"卡"。

我需要一个方案:高并发、低 CPU、单文件分发、Windows 原生 GUI。Go 是唯一同时满足这四个条件的。

事实证明,网络 IO 密集型的工具,语言切换带来的性能提升远超直觉预期。Go 的 goroutine 不是线程——它是"恰好让 IO 密集场景表现最优"的并发模型。




二、技术栈选择的真实考量

选 Go 不是跟风。我对比了三样东西:

维度Python (现状)Go (目标)C# (备选)
并发模型GIL 瓶颈,线程池上限 ~200goroutine 轻松 800+,~2MB 栈async/await + ThreadPool,也不错
部署形态打包 PyInstaller,50MB+ 体积单文件 EXE,-ldflags 裁剪后 ~10MB依赖 .NET Runtime
GUI 生态Tkinter(内置但陈旧)lxn/walk(Windows 原生)WPF/WinForms,成熟但绑定平台
网络库requests,生态好但同步阻塞net/http + x/net,标准库即够用HttpClient,也足够


Go 的劣势是 GUI 生态弱。lxn/walk 这个库作者已经停更了,最后 commit 停在 2021 年。但好在它本质是 Win32 API 的薄封装,Windows 11 上依然能跑,而且给了我最想要的东西:原生控件、主题感知、DPI 自适应。用 `proxytool.exe.manifest` 开启 DPI 感知后,高分屏上不再模糊——这一点直接毙掉了 Tkinter。

踩坑预警: lxn/walk 的 `declarative` 子包用链式 API 构建 UI,看起来很优雅,但错误信息极其不友好——控件 ID 冲突时报的是 native access violation,而不是 "duplicate widget"。调试 GUI 布局时建议用 `walk.MainWindow.SetSize` 显式设置窗口大小,别指望它自动计算,自动算出来的经常偏小一截。




三、代理采集:在国内网络环境下的生存之道

原版 Python 直接从 GitHub raw 地址拉代理列表。Python 版有 21 个源,包括 `raw.githubusercontent.com`、`proxyscrape.com`、`proxy-list.download`、`openproxy.space`。

切到 Go 后第一轮测试,21 个源里 18 个超时。`raw.githubusercontent.com` 在国内 DNS 污染严重,`proxyscrape` 和 `openproxy.space` 的 API 直接 TCP reset。能用的只剩 3 个。

这不是 Go 的问题。这是你在国内写网络工具时必须面对的第一课:任何指向境外直连的 URL 都是不可靠的。

解决方案是双镜像兜底:

[] 纯文本查看 复制代码
// 按序兜底:ghfast.top快(~1.4s),失败再试ghproxy.net
var ghMirrors = []string{
    "https://ghfast.top/https://raw.githubusercontent.com/",
    "https://ghproxy.net/https://raw.githubusercontent.com/",
}

func ghRaw(path string) []string {
    out := make([]string, len(ghMirrors))
    for i, m := range ghMirrors {
        out = m + path
    }
    return out
}


每个 GitHub 源生成两个镜像 URL,`collector.go` 里按序尝试,拿到数据就停。ghfast.top 平均响应 ~1.4s,ghproxy.net 备用。被墙的 `proxyscrape`/`openproxy` 直接移除——不是镜像能解决的,它们是整站被 reset。

最终保留 11 个 GitHub 仓库源(TheSpeedX/SOCKS-List、clarketm/proxy-list、monosans/proxy-list、hookzof/socks5_list、roosterkid/openproxylist 等),全部走镜像。

踩坑:最初我把镜像 URL 放在一个 `map` 里随机选,结果 `ghproxy.net` 时有不可用,约 15% 的轮次拿不到数据。改成确定性序后稳定 100% 命中。不要迷信"故障转移"——在网络不可靠的场景下,确定性的优先级列表比随机选择更可靠。




四、验证引擎:从 200 线程到 800 协程的跃迁

这是重写收益最大的部分。

Python 版验证逻辑:`ThreadPoolExecutor(max_workers=200)` → `requests.get(proxy, timeout=5)` → 回主线程更新 UI。问题在于:

1. `concurrent.futures` 的线程是真 OS 线程,200 个线程的上下文切换开销可观
2. `requests` 是同步阻塞的,每个线程在等待 IO 时占用完整线程栈
3. 结果通过 `as_completed` 逐个回调,UI 更新频率不可控

Go 版的方案:

[] 纯文本查看 复制代码
// 800协程持久池,channel驱动
for i := 0; i < VerifyThreads; i++ {
    go func() {
        for addr := range verifyChan {
            elapsed := tryConnect(addr, VerifyTimeout)
            outcomeChan <- verifyOutcome{addr, elapsed, ...}
        }
    }()
}

// UI端:150ms定时批量刷新
go func() {
    ticker := time.NewTicker(150 * time.Millisecond)
    for range ticker.C {
        app.mw.Synchronize(func() {
            app.model.PublishRowsReset()
            app.updateStats()
        })
    }
}()


关键改动:

  • 持久协程池:不销毁重建,800 个 goroutine 在 `select` 上阻塞等待 channel,切换开销接近于零
  • 连接超时降到 1s:死代理连不上 1 秒就放弃,不等 5 秒 DNS 超时
  • 流水线即时更新:每轮开始立即把全部代理丢进验证队列,各源边抓边验,不等到"所有源都抓完"
  • UI 批量刷新:`Synchronize` 回到主线程,150ms 批量重绘,不逐条更新


实测数据:首个可用结果 ~58ms 出现在列表中。Python 版同样场景下需要 2~4 秒——因为它在等 `as_completed` 逐个回调。Go 版 CPU 占用始终稳定在 1~3%,800 协程在 4 核机器上几乎没有调度压力。

踩坑 2:最初用 `time.Ticker` 做 UI 刷新,没有加 `Synchronize`,直接在 goroutine 里调 `PublishRowsReset()`。结果随机触发 access violation——walk 的 TableView 不是线程安全的。`Synchronize` 本质是向 Windows 消息队列 `PostMessage` 一个回调,保证在主线程执行。这是任何一个 Windows GUI 框架的铁律,但 walk 的文档完全没有提到这一点。




五、纯真 IP 库:GBK 编码的跨语言之痛

`qqwry.dat` 是中文社区最常用的离线 IP 归属地库,26MB,纯二进制格式。Python 版直接用 `qqwry` 包,一行 `query(ip)` 搞定。

Go 版——没有现成的库。我参考 qqwry 的逆向文档手写了解析器。核心数据结构是一个二叉查找树,索引区存了每条记录的文件偏移:

[] 纯文本查看 复制代码
func (q *QQwry) Query(ipStr string) (country, city string) {
    ip := ipToUint32(net.ParseIP(ipStr).To4())
    offset := q.search(ip, q.indexStart, q.indexEnd)
    return q.readRecord(offset)
}

func (q *QQwry) search(ip uint32, low, high uint32) uint32 {
    // 二分查找索引区,每条7字节:4字节IP + 3字节偏移
    for low <= high {
        mid := low + (high-low)/7*7
        midIP := q.readUint32(mid)
        if ip > midIP {
            low = mid + 7
        } else {
            high = mid - 7
        }
    }
    return q.readUint24(high + 4) // 返回记录偏移
}


GBK 解码才是真正的坑。qqwry.dat 中的地区字符串是 GBK 编码,Go 标准库没有 GBK 支持。用 `golang.org/x/text/encoding/simplifiedchinese` 可以解,但有些边界情况:

  • 某些记录的字符串以 NUL 字节结尾。先截断再解码,否则 `GBK.NewDecoder().Bytes()` 遇到 NUL 会直接返回原样,不解码
  • 库中有些"保留"区域的记录是空字符串,返回 `" CZ88.NET"` ——这是 qqwry 官方的标记,不是错误
  • 26MB 全部加载到内存的 `[]byte`,用 `binary.LittleEndian` 做偏移读取,不建额外索引——纯内存二分,单次查询 ~2us


在 Go 里处理中文编码一直是个微妙话题。标准库不包含 GBK 是合理的设计选择,但当你需要处理国内遗留数据时,`x/text` 就是必经之路。好在它是官方子仓库,质量和维护都有保障。




六、ProxyProbe:另一个维度的工具

proxytool_go 解决的是"日常使用"。但当我需要在一个 `/16` 网段里全端口扫描代理,而且目标网络有 IDS 的时候,GUI 工具就完全不够用了。

于是有了 ProxyProbe——一个独立的 CLI 工具,核心设计目标是隐蔽性

它的数据管道分两层:

  • Provider 层:三种目标源——RangeProvider(IP 网段)、ScraperProvider(URL 抓取)、FileProvider(历史结果优先复测)
  • Pipeline 层:TCP Ping 预过滤 → 全局洗牌 → 令牌桶限速 → 随机抖动 → TCP 端口扫描 → 协议验证


调度器的实现是整个工具的灵魂:

[] 纯文本查看 复制代码
func (s *Scheduler) Dispatch(ctx context.Context) <-chan models.Target {
    out := make(chan models.Target, 1000)
    go func() {
        defer close(out)
        for _, t := range s.targets {
            // 令牌桶:严格限制 PPS
            if s.limiter != nil {
                s.limiter.Wait(ctx)
            }
            // 随机抖动:毫秒级延迟
            if s.cfg.SlowMode && s.cfg.Jitter > 0 {
                delay := time.Duration(s.rng.Intn(s.cfg.Jitter)) * time.Millisecond
                time.Sleep(delay)
            }
            out <- t
        }
    }()
    return out
}


全局洗牌在 `prepareTargets` 中完成——把所有目标对 `[IP1:80, IP1:8080, IP2:80...]` 用 `math/rand.Shuffle` 彻底打乱。这样当限速 100 PPS 时,同一个 IP 的不同端口不会连续出现在线路上,避免了按 IP 聚集的特征。

严格 HTTPS 验证是另一个硬骨头。很多公开代理会伪造 200 OK 响应或者劫持 HTTPS 流量。ProxyProbe 的 `strict_transport.go` 手动实现了 CONNECT 隧道 + TLS 握手:

[] 纯文本查看 复制代码
// 1. TCP拨号到代理
conn, _ := net.DialTimeout("tcp", proxyAddr, timeout)
// 2. 发送 CONNECT 请求
fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, addr)
// 3. 验证 CONNECT 响应码
resp, _ := http.ReadResponse(bufio.NewReader(conn), nil)
if resp.StatusCode != 200 {
    return nil, fmt.Errorf("proxy refused CONNECT: %d", resp.StatusCode)
}
// 4. 在隧道上执行 TLS 握手
tlsConn := tls.Client(conn, &tls.Config{ServerName: host})
tlsConn.HandshakeContext(ctx)


只有真正透传了 TLS 握手的代理才通过验证。伪造 200 的代理在第三步就会暴露——它们不等 `CONNECT` 就返回响应。

这个方案有一个取舍:如果代理本身就是 HTTP-only(不支持 CONNECT),它会被直接拒绝。但对于需要 HTTPS 的场景来说,一个不能建 CONNECT 隧道的代理跟不可用没有区别。




七、数据可靠性:原子写入的必要性

代理工具的验证结果文件 (`valid_proxies.txt`) 每轮都会全量重写。如果在写入过程中用户关了窗口、或被系统杀掉进程,半个文件留在磁盘上就会导致下次启动时解析失败。

Go 版的解决很简单但有效:

[] 纯文本查看 复制代码
func saveProxies(path string, proxies []*Proxy) error {
    tmp := path + ".tmp"
    // 全部写入临时文件
    f, _ := os.Create(tmp)
    bufio.NewWriter(f).WriteString(...)
    f.Sync()  // 确保落盘
    f.Close()
    // 原子替换
    return os.Rename(tmp, path)
}


`os.Rename` 在 Windows NTFS 上是原子的——要么旧文件在,要么新文件在,不存在半个文件的状态。这个模式复制自 SQLite 的 WAL 策略,简单但异常可靠。

不要相信任何"先打开再清空再写入"的模式。如果一个工具需要长期运行并定期持久化,原子写入是唯一正确的选择。中间状态的存在窗口哪怕只有几毫秒,在足够长的运行时间里也一定会被触发。




八、总结:两个工具,两种哲学

维度proxytool_go (GUI)ProxyProbe (CLI)
目标用户日常代理使用者渗透测试、安全运维
并发模型800 协程持久池可配置并发 + 令牌桶限速
隐蔽性不关注全局洗牌 + 随机抖动,核心设计目标
协议验证基础 HTTPHTTP + SOCKS5 + 严格 HTTPS (CONNECT+TLS)
部署形态单文件 EXE,双击运行CLI 二进制,支持守护进程
生态定位"代理的日常监控面板""低噪探测与批量验证引擎"


两个工具共享同一个数据文件格式 (`valid_proxies.txt`),可以互换使用。采集到一批代理后直接用 ProxyProbe 做深度验证,成果无缝回流到 proxytool_go 继续监控。

项目已完整开源,代码中每一行都是为实际场景写的——没有为了"整洁架构"引入不必要的抽象,也没有因为是工具类项目就降低质量标准。




八、源码下载
游客,如果您要查看本帖隐藏内容请回复


易语言】jadeView前端UI:1103426302


评分

参与人数 7精币 +7 收起 理由
zjbin1989 + 1 感谢分享,很给力!~
xhping + 1 感谢分享,很给力!~
风雨3137 + 1 感谢分享,很给力!~
cui870222829 + 1 感谢分享,很给力!~
cbl521ysys + 1 感谢分享,很给力!~
恒大大 + 1 感谢分享,很给力!~
文西哥 + 1 感谢分享,很给力!~

查看全部评分


签到天数: 27 天

发表于 昨天 23:44 | 显示全部楼层   河北省石家庄市
感谢分享
回复 支持 反对

使用道具 举报

签到天数: 19 天

发表于 昨天 23:12 | 显示全部楼层   四川省成都市
回复 支持 反对

使用道具 举报

结帖率:27% (3/11)

签到天数: 10 天

发表于 昨天 21:13 | 显示全部楼层   山东省烟台市
511623213211321232313211313
回复 支持 反对

使用道具 举报

结帖率:100% (1/1)

签到天数: 3 天

发表于 昨天 20:32 | 显示全部楼层   江苏省苏州市
谢谢分享!!!
回复 支持 反对

使用道具 举报

签到天数: 8 天

发表于 昨天 20:32 | 显示全部楼层   广东省惠州市
感谢分享,很给力!~
回复 支持 反对

使用道具 举报

签到天数: 4 天

发表于 昨天 18:14 | 显示全部楼层   广东省广州市
666666666666
回复 支持 反对

使用道具 举报

结帖率:50% (2/4)

签到天数: 2 天

发表于 昨天 16:08 | 显示全部楼层   河南省新乡市
花老板辛苦了
回复 支持 反对

使用道具 举报

结帖率:67% (14/21)

签到天数: 15 天

发表于 昨天 16:01 | 显示全部楼层   湖南省长沙市
感谢分享!
回复 支持 反对

使用道具 举报

结帖率:75% (3/4)

签到天数: 4 天

发表于 昨天 15:38 | 显示全部楼层   湖北省武汉市

回复看一下吧
回复 支持 反对

使用道具 举报

结帖率:100% (21/21)

签到天数: 11 天

发表于 昨天 15:32 | 显示全部楼层   广东省汕头市
        感谢分享,很给力!~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报QQ: 793400750,邮箱:wp@125.la
网站简介:精易论坛成立于2009年,是一个程序设计学习交流技术论坛,隶属于揭阳市揭东区精易科技有限公司所有。
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备2025452707号) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表