Rust写Android原生代理拆解tg-ws-proxy-android的JNI FFI接口与tokio运行时设计【免费下载链接】tg-ws-proxy-androidAndroid-форк популярного приложения Flowseal - tg-ws-proxy - локальный прокси-сервер MTProto с проксированием CF или без для частичного обхода проблем загрузки Telegram项目地址: https://gitcode.com/gh_mirrors/tg/tg-ws-proxy-androidtg-ws-proxy-android是一个把Rust 原生代理内核打进 APK 的 Android 应用Kotlin 界面负责交互真正的 MTProto 代理、WebSocket 连接池、TLS 握手全部由 Rust 编写的libtgwsproxy.so完成。这篇文章带你完整拆解它的JNI FFI 接口层与tokio 多线程运行时是如何协作的帮助想学习 Rust 移动原生开发的人快速理解这条技术路线。1. 为什么用 Rust 写 Android 代理内核用原生语言写网络代理内核收益非常直接无 GC 停顿代理要转发大量小包Rust 没有垃圾回收延迟稳定零成本抽象async/await编译为状态机没有虚拟机的额外开销产物极小项目用 Cargo.toml 中opt-level zLTOpanic abortstrip的发布配置把体积压到极致最终就是 app/src/main/jniLibs/arm64-v8a/libtgwsproxy.so 这一个文件内存安全FFI 边界是 C 程序最容易崩溃的地方Rust 的类型系统在库内部把大多数坑堵死整体数据流如下Telegram 客户端 → 本地 MTProto (127.0.0.1:1443) → libtgwsproxy.so (Rust tokio 内核) → WSS (经 CloudFlare 或直连) → Telegram 数据中心2. 接口层JNA 如何跨语言调用 Rust传统 JNI 需要手写jclass、jstring样板代码而本项目选择了更轻的JNAJava Native Access路线Kotlin 侧只需声明一个接口JNA 自动完成函数签名匹配与参数编组。Kotlin 侧的全部JNI 代码就在 NativeProxy.ktinterface ProxyLibrary : Library { companion object { val INSTANCE Native.load(tgwsproxy, ProxyLibrary::class.java) as ProxyLibrary } fun StartProxy(host: String, port: Int, dcIps: String, secret: String, verbose: Int): Int fun StopProxy(): Int fun SetPoolSize(size: Int) fun GetStats(): Pointer? fun FreeString(p: Pointer) // ... }Native.load(tgwsproxy, ...)会自动找到 jniLibs 下的libtgwsproxy.so并加载之后每个方法调用都是一次普通的 C ABI 函数调用——接口名必须和 Rust 导出的符号完全一致。2.1 Rust 侧的导出约定Rust 侧通过#[no_mangle]extern C导出符号核心接口集中在 src/lib.rs接口类型作用StartProxy(host, port, dcIps, secret, verbose)启动绑定本地端口并启动代理主循环返回 0 表示成功StopProxy()停止取消全部任务并等待优雅退出SetPoolSize(n)/SetBufferSizeKb(kb)调参运行中热更新连接池大小、收发缓冲区SetCfProxyConfig(...)/SetFakeTlsDomain(...)调参配置 CloudFlare 转发域与 FakeTLS 伪装域GetStats()/GetSecretWithPrefix()查询返回统计摘要与带前缀的代理密钥FreeString(p)内存释放 Rust 分配的 C 字符串跨语言传字符串统一用 C 字符串Rust 侧的cstr_to_string把*const c_char安全转成String空指针会被容忍为空串见 src/lib.rs。2.2 一块值得学习的内存所有权设计注意GetStats()的返回值——它不是拷贝而是把 Rust 的CString转成裸指针交给 KotlinRust 分配CString::new(s).into_raw()src/lib.rsKotlin 读取ptr.getString(0)后必须回调FreeString(p)NativeProxy.kt谁分配谁释放FreeString内部用CString::from_raw把所有权接回来再析构。这是 FFI 库最经典的跨语言所有权协议也是这类项目里最容易踩内存泄漏/双重释放坑的地方本项目的做法非常干净。3. tokio 运行时设计一个永不销毁的全局事件循环整个代理内核跑在一个全局静态的 tokio 多核运行时上构建逻辑在 src/lib.rsstatic RUNTIME: OnceCellRuntime OnceCell::new(); fn runtime() - static Runtime { RUNTIME.get_or_init(|| { tokio::runtime::Builder::new_multi_thread() .worker_threads(4) // 手机够用即可 .thread_name(tgwsproxy-rt) .enable_all() .build() .expect(failed to build global tokio runtime) }) }三个关键设计决策①OnceCell保证单例且永不 drop。运行时只创建一次StopProxy时只取消任务、不销毁运行时——这样用户可以停止再启动避免反复构建/销毁多线程运行时的开销与竞态。② 固定 4 个 worker 线程。手机 CPU 大小核调度活跃4 个 worker 足以并行处理多路 WebSocket 桥接每条连接的上行/下行/keepalive 各是一个 task又不会挤占前台 UI 线程。③ 同步接口 vs 异步内核的握手。StartProxy是同步的 C 函数但TcpListener::bind是异步的。项目用一个std::sync::mpsc通道解决先rt.spawn出代理主任务主线程阻塞在rx.recv()上等 bind 成功才返回 0bind 失败则handle.abort()并返回 -3src/lib.rs。Kotlin 侧因此可以同步拿到确定性的启动结果无需自己轮询。3.1 优雅停止CancellationToken 2 秒超时停止流程src/lib.rs是教科书级的结构化并发实践cancel_tasks.cancel()——一个CancellationToken克隆到所有任务里任何tokio::select!分支都会响应它退出rt.block_on等待主任务timeout(2s)兜底防止个别卡死的 task 让 UI 线程永久挂起pools.close_all()关闭连接池里所有 WebSocket重置黑名单与统计运行时本身保留等待下次StartProxy。连接池的预连接、过期轮换、指数退避重试1s→3600s全部由 tokio task 驱动实现在 src/proxy.rs 的WsPool中其中甚至专门用同步函数包裹tokio::spawn来打破 async 类型循环依赖src/proxy.rs 的注释解释了这个 E0391 编译错误的解法。3.2 运行时无关的调参通道配置项池大小、缓冲区、CF 代理开关等以AtomicBool/AtomicI32RwLock全局量存放于 src/config.rsKotlin 侧通过SetXxx接口随时热更新代理任务在下一个循环读取新值——无需重启代理这是 FFI 分层带来的典型好处状态机与 UI 彻底解耦。4. 构建流程cargo-ndk 一条命令出 so编译脚本 build_so.bat 展示了标准流程cargo ndk -t arm64-v8a --platform 24 -o app/src/main/jniLibs build --release cargo ndk -t armeabi-v7a --platform 21 -o app/src/main/jniLibs build --release要点crate-type [cdylib]Cargo.toml只产出动态库这正是 Android 加载的形式分别针对 arm64-v8aAPI 24与 armeabi-v7aAPI 21两个架构交叉编译产物直接放进 jniLibs随 APK 分发Kotlin 侧零改动5. 关键文件速查想动手跟读源码按这个顺序效率最高文件看什么src/lib.rs全部 FFI 导出函数、运行时单例、启停握手app/src/main/java/com/amurcanov/tgwsproxy/NativeProxy.ktJNA 接口声明与 Rust 符号一一对应src/proxy.rsWS 连接池预连接、轮换、退避src/ws.rsrustls TLS 配置与会话复用缓存src/config.rs全部超时常量与全局配置状态build_so.batcargo-ndk 交叉编译与 target 安装6. 小结这条技术路线的三层结构FFI 边界层extern C导出 JNA 声明字符串用 C 字符串、指针有明确的FreeString回收协议运行时层全局OnceCellRuntime单例4 worker 线程任务用CancellationToken统一取消启停走同步入口 异步内核握手业务层连接池、桥接、TLS 全部是纯 async task通过原子量读取运行中热更新的配置。如果你想给自己的 Android 应用加一个 Rust 高性能内核——无论是代理、加密还是协议解析——这个项目就是一个可以直接抄作业的完整样板JNA 省掉 JNI 样板OnceCell全局运行时省掉生命周期管理而cargo-ndk让构建只需两条命令。【免费下载链接】tg-ws-proxy-androidAndroid-форк популярного приложения Flowseal - tg-ws-proxy - локальный прокси-сервер MTProto с проксированием CF или без для частичного обхода проблем загрузки Telegram项目地址: https://gitcode.com/gh_mirrors/tg/tg-ws-proxy-android创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考