本文主要讲解,ST 提供的示例程序 proxy
,make
编译之后,会在 obj
目录生成 proxy
可执行文件,如下:
proxy
是一个流量代理程序,可以把TCP流量转发给远程服务器,使用命令如下:
用 -X 是只开启一个进程,简单起见,先讲解单进程的逻辑。
./obj/proxy -X -l 192.168.0.122:8999 -r 124.223.94.246:80
上面的命令,会把所有访问 192.168.0.122
机器的 8999
端口的TCP流量,全部转发给 124.223.94.246:80
。
接下来,用 Clion
搭建好 proxy
的可视化调试环境,跟之前的 lookupdns
一样,可以看之前的文章《ST源码分析-Clion调试》
proxy
程序的单进程逻辑 比较简单,流程图如下:
从上图可以看到,除了 idle
协程,其他所有协程做的事情都是对于 在 系统线程 看来都是非阻塞,非阻塞协程干的话,就是把 需要监听的 fd
放进去全局变量 fd_set
,然后由 idle
协程 执行 select()
函数阻塞,等待信号。ST的调度策略是 _ST_RUNQ
里面已经没有协程要运行了,才会切换到 idle
协程来阻塞。
proxy
程序的单进程逻辑 看上面的流程图,跟自己看代码就能理解,比较简单。
下面开始讲解 proxy
的多进程模式。只需要 把 -X
命令行参数去掉,就会执行 set_concurrency()
函数,如下图:
去掉 -X 参数之后, proxy
就会调用 start_daemon()
函数 进入守护进程模式,clion
就不好调试了,只能用
gdb
。
相比于单进程模式,多进程 只是多掉了两个函数 。
1,start_daemon()
,进入守护进程模式
2, set_concurrency()
,开启多进程处理请求。
所以 重点分析一下 set_concurrency()
函数就足够了,代码如下图:
上图代码很简单,没有 watchdog
机制,只是 fork()
了好几个进程。下面就来讲解 fork
这么多进程之后,对后续的逻辑有什么影响。
fork 之后,后续的进程 会把变量srv_nfd
都拷贝一份,但是 fd
本身是一个 int
,只是一个底层描述符列表的索引,可以理解为指针,即使是多进程,系统 fd
底层还是用的同一个。也就是说 多个进程 的 idle
协程 同时 select
监听这个 srv_nfd
。
我们看一下这种情况,如果一个客户端来了,是不是所有 进程的 select()
都会激活。在 select()
函数后面 加入 调试代码,如下:
/* Open and write */
char file_name[80] = "/home/ubuntu/Documents/st-1.9/obj/pid_";
char pid_name[20] = {0};
sprintf(pid_name,"%d",(int)getpid());
strcat(file_name,pid_name);
int log_fd = open(file_name, O_CREAT | O_WRONLY | O_TRUNC, 0644);
write(log_fd, file_name, strlen(file_name));
close(log_fd);
如果触发惊群,一个客户端来了,有多少个进程就会创建多少个 pid_xxx
文件。我是 16 核,所以是16进程。
如上图所示,只有两个文件,一个是本地客户端的,一个是远程触发的。所以多进程 select()
同一个套接字不会触发惊群。
epoll_wait()
的惊群讨论,推荐阅读以下文章:
1, 《关于linux中select和epoll是否存在惊群效应的争执》
2,《Epoll 新增 EPOLLEXCLUSIVE 选项解决了新建连接的’惊群‘问题》
proxy 多进程分析完毕。
相关阅读:
由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。QQ:2338195090。