TCP代理服务器proxy程序分析—StateThreads示例程序介绍

proxy 是官网提供的示例程序,演示了如何使用 StateThread 协程来实现一个 TCP 代理服务器,也就是一个 TCP 流量转发程序。

proxy 的用法如下:

复制代码
proxy -X -l 192.168.0.109:8999 -r 124.223.94.246:80

-X 代表只开启一个进程,也不会以守护进程启动,方便调试。

这样 192.168.0.109 就是 124.223.94.246 的代理服务器了,当访问局域的 192.168.0.109 机器的 8999 端口的时候,就相当于访问 124.223.94.246:80

假设浏览器是客户端,实际上就是把 浏览器发往 192.168.0.109:8999 的 TCP 流量转发给 124.223.94.246:80,然后再把 124.223.94.246:80 发给 192.168.0.109:8999 的TCP 流量转发给浏览器。


proxy 程序的代码文件只有一个,就是 proxy.c,读者可以参考《StateThreads调试环境搭建》搭建好 clion 的调试环境

proxy 程序的主要流程如下:

上面我省略了 _st_thread_main() ➔ thread->start() ➔ st_thread_exit() 的步骤没画出来的。

上图中是一个 多协程 处理请求的架构,当浏览器发送一个请求来的时候,st_accept() 就会创建一个 代理服务器 192.168.0.109:8999 跟 浏览器 通信的 TCP 连接(socket),我们把它成为 TCP Socket1。

然后再创建一个 handle_request() 协程来处理这个请求,把 TCP Socket1 丢给 handle_request() 协程。

handle_request() 内部会开始建立 代理服务器 跟 源站 124.223.94.246:80 的 TCP 连接,我们把它成为 TCP Socket2。

现在 handle_request() 内部就有 两个 TCP Socket 了,然后再调 st_poll() 来监控这两个 TCP Socket 的数据,当浏览器有数据来的时候,就把 数据从 Socket1 读取出来,然后写入 Socket2。当源站有数据来的时候,就把 数据从 Socket2 读取出来,然后写入 Socket1。

这就是整个 proxy 代理服务器的工作流程。


上图中的函数流程是 跟 多线程编程范例 的函数流程非常类似的,基础的网络编程的知识请阅读《Unix网络编程》

你可以把 st_thread_create() 换成 pthread_create() 函数,再改造一下这个程序,就变成了多线程架构,但是多线程消耗的资源是相对较多的。

上图的架构是单线程多协程的,因为我在命令行里使用了 -X 选项,所以只会创建一个进程。如果你想这个 proxy 程序能利用到多核,需要去掉 -X,这样他就会根据 CPU 核数来创建多个进程,这样就能变成了 多进程多协程 架构,或者说是 多线程多协程 架构,因为每个进程里面都有一个线程。


proxy 代理服务器的重点是 st_accept() 跟 st_poll() 函数,因为这两个函数是阻塞函数,阻塞就会进行协程切换。

假设现在有 3 个浏览器客户端跟 代理服务器 建立了 TCP 连接,那现在就有 3 个 handle_request() 协程在运行,还有一个 始祖协程 main 在阻塞在 st_accept() 里。

我个人喜欢把 main() 称为始祖协程。

当始祖协程 执行到 st_accept() 函数的时候,会立即看一下有没有浏览器请求,如果有就不进行协程切换,而是继续往下走。如果没有浏览器请求,就会调 st_netfd_poll() 把 始祖协程 的 listen fd 放进去 全局的 Socket 集合 ,然后切换到 协程管理程序 ,查看一下有没其他的协程需要运行的,如果有就切换到其他协程,例如切换到 handle_request() 。

当 协程1 handle_request 执行到 st_poll() 的时候,也会把他的两个 Scocket fd 放进去 全局的 Socket 集合 ,然后切换到 协程管理程序,查看一下有没其他的协程需要运行的,如果有就切换到其他协程。

如果所有的 handle_request 协程 跟 始祖协程 都不需要继续运行的时候,就会切换到 idle 协程用 selec() 或者 epoll_wait() 来监控 全局的 Socket 集合,那个 socket fd 有数据到了就激活哪个协程。

如果有第四个浏览器发起 TCP 请求,就激活 始祖协程 main() ,从 st_accept() 继续往下跑。

如果源站 124.223.94.246:80 有数据到了,就激活 handle_request() 协程,从 st_poll() 函数开始继续往下跑。


proxy 程序默认使用的是 select() 函数来监控 IO 事件,我们也可以用 -a 选项来让他使用 epoll 来监控 IO 事件,命令如下:

css 复制代码
proxy -a -X -l 192.168.0.109:8999 -r 124.223.94.246:80

选项 IO 事件驱动是用的 st_set_eventsys() 函数的。

scss 复制代码
if (alt_ev)
    st_set_eventsys(ST_EVENTSYS_ALT);

扩展知识,proxy 里面有一个 cpu_count() 函数,可以用来获取 CPU 的核数,非常好用。

ini 复制代码
static int cpu_count(void) {
    int n;

#if defined (_SC_NPROCESSORS_ONLN)
    n = (int) sysconf(_SC_NPROCESSORS_ONLN);
#elif defined (_SC_NPROC_ONLN)
    n = (int) sysconf(_SC_NPROC_ONLN);
#elif defined (HPUX)
#include <sys/mpctl.h>
    n = mpctl(MPC_GETNUMSPUS, 0, 0);
#else
    n = -1;
    errno = ENOSYS;
#endif

    return n;
}

本文是《 SRS原理 》一书中的文章,如需观看更多内容,请购买本书。

相关推荐
屏什么录5 天前
录屏超过两小时就卡顿崩盘?从参数调优到代码实现,一文彻底搞定长时间录屏
前端·音视频开发
字节跳动视频云技术团队6 天前
720P 直升 8K,解密 2026 抖音美好奇妙夜巨幕超分黑科技
人工智能·音视频开发
VimorphTech10 天前
文件名是 MP4 就能处理?用 ffprobe 检查视频流、音轨和时长
java·音视频开发
冬奇Lab12 天前
开源项目第242期:HyperFrames —— 写 HTML 就能渲染视频,专为 AI Agent 设计的确定性视频框架
人工智能·开源·音视频开发
屏什么录17 天前
电脑如何录屏幕视频和声音?从工具到代码,7 种方案帮你彻底搞定
前端·音视频开发
VimorphTech18 天前
转写结果怎么生成双语字幕?TypeScript 实现 SRT、WebVTT 与索引匹配
javascript·音视频开发
音视频牛哥23 天前
人形机器人的未来发展趋势是什么?从VLA、实时视觉到规模化运营
机器学习·机器人·音视频开发
霍营_托尼23 天前
纯前端播 40GB 本地视频?我把浏览器改造成了「磁盘流式」播放器
前端·javascript·音视频开发
花椒技术1 个月前
2.46 秒生成 5 秒视频:拆解 H3 Max 的模型、推理栈与硬件协同
算法·音视频开发·视频编码
代码小学僧1 个月前
从 0 到 1,记录我的第一个出海 SaaS 独立开发项目
前端·rpc·音视频开发