ST源码分析-server

SRS 的社群来了,想加入微信社群的朋友请购买《SRS原理》电子书,里有更高级的内容与答疑服务。


本文主要讲解,ST 提供的示例程序 server ,make 编译之后,会在 obj 目录生成 server 可执行文件,如下:

server 是一个 简单的 http 服务器,访问之后输出一个 简单的 html 页面,使用命令如下:

./obj/server -l ./ -b 192.168.0.122:8888

在刚开始调试的时候,推荐加上 -i 选项,可以防止 server 在后台运行。


server 流程图如下:

上面流程几个比较简单的函数,我简单过一遍:

1,parse_arguments(),解析命令行参数到 变量。

2,set_thread_throttling(),就根据一些规则设置 3 个变量,max_threadsmin_wait_threadsmax_wait_threads

3,create_listeners(),创建套接字,然后 listen,这里可以绑定多个 ip 端口 。

4,open_log_files(),创建 pid 文件,这里 pid 文件好像没有限制两个程序同时允许。最重要的是打开 ERRORS_FILE 错误文件。

5,load_configs() ,什么都没做,留给用户实现的。


先讲解一下 server 程序 中的一个重点结构体 struct socket_info

struct socket_info {
  st_netfd_t nfd;               /* Listening socket                     */
  char *addr;                   /* Bind address                         */
  unsigned int port;            /* Port                                 */
  int wait_threads;             /* Number of threads waiting to accept  */
  int busy_threads;             /* Number of threads processing request */
  int rqst_count;               /* Total number of processed requests   */
} srv_socket[MAX_BIND_ADDRS];   /* Array of listening sockets   

上面的代码定义了一个 全局数据srv_socket ,这个数组一般只有 [0] 用到,只绑定一个 ip跟端口。这个结构体的每个字段都比较重要,下面详细讲解一些:

1, st_netfd_t nfd; 这个是 服务器 listen 的 tcp 套接字,ST 自己封装了一下。

2, char *addr; IP 地址

3,unsigned int port; 端口。

4,int wait_threads; 代表有多少个协程 阻塞在 st_accept() 函数 。

5,int busy_threads 代表有多少个协程已经 从 st_accept() 拿到 fd,开始处理 http 请求。

6,int rqst_count;代表处理了多少个 http 请求。


server 程序有3个重点函数。

1,start_processes() ,开启多进程。多个进程同时 select() 监听 服务端套接字(srv_socket[0].nfd)。

2,install_sighandlers() ,注册信号 SIGTERMSIGHUPSIGUSR1, 用 pipe() 管道把 信号事件 转成 I/O 事件,这样就能用 select 监听 管道的 fd ,跟其他的网络套接字同一个 select 监听。

Only two types of external events are handled by the library's scheduler, because only these events can be detected by select(2) or poll(2): I/O events (a file descriptor is ready for I/O) and time events (some timeout has expired). However, other types of events (such as a signal sent to a process) can also be handled by converting them to I/O events. For example, a signal handling function can perform a write to a pipe (write(2) is reentrant/asynchronous-safe), thus converting a signal event to an I/O event

3,start_threads() ,开启 max_wait_threads 数量的协程,全部阻塞在 st_accept() 函数 等待客户端请求。


下面仔细分析重点函数 start_processes() ,流程图参考上面的,重点如下。

1,start_processes() 会 fork() 很多子进程,所有子进程都会返回 main() 函数执行后续的逻辑。但是 父进程不返回 main() 函数,父进程一直阻塞在 start_processes() 函数里面,等待某个子进程结束或者意外终结,然后打印子进程退出信息,在 fork() 一个子进程。这样,父进程 就是一个 watchdog,看门人,子进程如果有代码问题,奔溃了,父进程就会被激活,收集子进程的退出信息,方便排查错误,然后父进程再生一个子进程出来处理业务。

这种 watchdog的机制特别好,因为写代码总有一些意外的情况,不是经常发生。偶尔奔溃,就子进程奔溃,那就由父进程再生一个子进程出来就行。

在 Linux 环境,有一个软件也可以实现这种 看门人功能,就是 supervisor

父进程除了等待子进程结束,还会处理信号,如下面代码,wait() 函数在等待子进程结束的时候,会被信号中断,errno 等于 EINTR 就会继续等待。

if ((pid = wait(&status)) < 0) {
     if (errno == EINTR)
continue;
     err_sys_quit(errfd, "ERROR: watchdog: wait");
   }

父进程是用 wdog_sighandler() 函数来处理信号的,所有的信号都会传递一遍给子进程,然后自己再处理信号。演示一下,我用以下命令 发送一个 SIGUSR1 信号给父进程 ,13287 是我的父进程ID。

kill -s SIGUSR1 13287

上面的命令执行之后,所有的进程都会打印一遍信息,如下:

server 程序有处理 3 种信号:

  1. SIGTERM,终结进程。
  2. SIGHUP,虽然注释写的 restart,但是实际上只是重新加载了配置文件。
  3. SIGUSR1 ,打印信息。

主进程不返回 main() , 子进程全部返回 main,然后 阻塞在 process_signals() 处理信号。

相当于 始祖协程 阻塞在 process_signals() 处理信号。 有多少个进程就有多少个始祖协程。


接下来分析最后一个重点函数 start_threads(),这个是子进程的开始协程的函数,用来处理http请求的,流程图参考上面的:

start_threads() 用 st_thread_create() 创建了很多个协程函数 handle_connections()。这些协程函数全部阻塞在 st_accept() 那里,等待客户端请求。


相关阅读:

  1. 《进程,线程,信号》

由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Loken2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值