TKeed Webserver 结构梳理

本文详细解析TKeed Webserver的源码,涵盖从main.cpp开始的各个关键函数,如read_conf、handle_for_sigpipe、socket_bind_listen等,涉及配置读取、信号处理、套接字监听、非阻塞模式、epoll创建、线程池初始化和工作线程等核心环节,深入理解Web服务器的工作原理。
摘要由CSDN通过智能技术生成


在这里插入图片描述

main.cpp

本篇从main.c中逐一解析函数、结构体(边记边学,欢迎指正)

#include <stdio.h>
#include "threadpool.h"
#include "http.h"

#define DEFAULT_CONFIG "tkeed.conf"

extern struct epoll_event *events;
char *conf_file = DEFAULT_CONFIG;
tk_conf_t conf;

// 读取配置文件

read_conf(conf_file, &conf); 
https://blog.csdn.net/jojozym/article/details/104825583  // read_conf函数详解

// 处理SIGPIPE

handle_for_sigpipe();
https://blog.csdn.net/jojozym/article/details/104825787  // handle_for_sigpipe详解

//初始化套接字开始监听

int listen_fd = socket_bind_listen(conf.port);
https://blog.csdn.net/jojozym/article/details/104826186   //socket_bind_listen函数详解

// 设置为socket非阻塞

int rc = make_socket_non_blocking(listen_fd);
https://blog.csdn.net/jojozym/article/details/104826653     //make_socket_non_blocking详解

//创建epoll并注册监听描述符

int epoll_fd = tk_epoll_create(0);
tk_http_request_t* request = (tk_http_request_t*)malloc(sizeof(tk_http_request_t));
tk_init_request_t(request, listen_fd, epoll_fd, conf.root);
tk_epoll_add(epoll_fd, listen_fd, request, (EPOLLIN | EPOLLET));
https://blog.csdn.net/jojozym/article/details/104834862      //tk_epoll_create详解
https://blog.csdn.net/jojozym/article/details/104835199      //tk_init_request_t详解
https://blog.csdn.net/jojozym/article/details/104835627      //tk_epoll_add详解

// 初始化线程池

tk_threadpool_t *tp = threadpool_init(conf.thread_num);
https://blog.csdn.net/jojozym/article/details/104861812      //threadpool_init详解

// 初始化计时器

tk_timer_init();

之后进入循环

  while(1){
        // 得到最近且未删除时间和当前时间差值(等待时间)
        int time = tk_find_timer();

        // 调用epoll_wait函数,返回接收到事件的数量
        int events_num = tk_epoll_wait(epoll_fd, events, MAXEVENTS, -1);

        // 处理已经超时的请求
        tk_handle_expire_timers();

        // 遍历events数组,根据监听种类及描述符类型分发操作
        tk_handle_events(epoll_fd, listen_fd, events, events_num, conf.root, tp);
    }

    // 回收线程资源
    // threadpool_destroy(tp, graceful_shutdown);
}

read_conf

read_conf函数在util.cpp中

#include "util.h"
#include "http_request.h"
#include "epoll.h"

函数定义如下:

int read_conf(char* filename, tk_conf_t* conf) {
	// 以只读方式打开文件
	FILE* fp = fopen(filename, "r");
	if (!fp)
		return TK_CONF_ERROR;

	char buff[BUFLEN];
	int buff_len = BUFLEN;
	char* curr_pos = buff;
	char* delim_pos = NULL;
	int i = 0;
	int pos = 0;
	int line_len = 0;
	while (fgets(curr_pos, buff_len - pos, fp)) {
		// 定位每行第一个界定符位置
		delim_pos = strstr(curr_pos, DELIM);
		if (!delim_pos)
			return TK_CONF_ERROR;
		if (curr_pos[strlen(curr_pos) - 1] == '\n') {
			curr_pos[strlen(curr_pos) - 1] = '\0';
		}

		// 得到root信息
		if (strncmp("root", curr_pos, 4) == 0) {
			delim_pos = delim_pos + 1;
			while (*delim_pos != '#') {
				conf->root[i++] = *delim_pos;
				++delim_pos;
			}
		}

		// 得到port值
		if (strncmp("port", curr_pos, 4) == 0)
			conf->port = atoi(delim_pos + 1);

		// 得到thread数量
		if (strncmp("thread_num", curr_pos, 9) == 0)
			conf->thread_num = atoi(delim_pos + 1);

		// line_len得到当前行行长
		line_len = strlen(curr_pos);

		// 当前位置跳转至下一行首部
		curr_pos += line_len;
	}
	fclose(fp);
	return TK_CONF_OK;
}

read_conf函数功能:
1、main.c传tkeed.conf给util.cpp
2、util.cpp负责从tkeed.conf中读取root、port、thread_num等信息,赋值给tk_conf_t* conf

tk_conf_t在util.h头文件中有定义。

typedef struct tk_conf {
	char root[PATHLEN];
	int port;
	int thread_num;
}tk_conf_t;

端口的取值范围是:0-65535。

在这个取值范围中1023以下的端口已经分配给了常用的一些应用程序,这个数字以后的端口部分被使用,所以网络编程可用的端口一般在1024之后选取。
在网络技术中,端口(Port)大致有两种意思:

1、物理意义上的端口,比如,ADSL Modem、集线器、交换机、路由器用于连接其他网络设备的接口,如RJ-45端口、SC端口等等;

2、逻辑意义上的端口,一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等。

handle_for_sigpipe

handle_for_sigpipe函数在util.c中
函数定义如下:

void handle_for_sigpipe() {
	struct sigaction sa; 
	memset(&sa, '\0', sizeof(sa));
	sa.sa_handler = SIG_IGN;//忽略信号,也可以选择另一个参数SIG_DFL,它俩都在signal.h里
	sa.sa_flags = 0;//只接收信号,不需要其他额外信息,设置为0
	if (sigaction(SIGPIPE, &sa, NULL))
}

SIG_DFL 默认信号处理
SIG_IGN 忽略信号

定义函数 int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
函数说明 sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum可以指定SIGKILL和SIGSTOP以外的所有信号。

结构体 sigaction定义如下:

struct sigaction {
          union {
                  __sighandler_t _sa_handler;
                  void (*_sa_sigaction)(int, struct siginfo *, void *);
          } _u;
          sigset_t sa_mask;
          unsigned long sa_flags;
          void (*sa_restorer)(void);
  };
  #define sa_handler   _u._sa_handler
  #define sa_sigaction _u._sa_sigaction
  #endif /* __KERNEL__ */

sa_handler的原型是一个参数为int,返回类型为void的函数指针。参数即为信号值,所以信号不能传递除信号值之外的任何信息;

sa_sigaction的原型是一个带三个参数,类型分别为int,struct siginfo *,void *,返回类型为void的函数指针。第一个参数为信号值;第二个参数是一个指向struct siginfo结构的指针,此结构中包含信号携带的数据值;第三个参数没有使用。

sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞。

sa_flags包含了许多标志位,比较重要的一个是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。即使sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误。

sa_restorer已过时,POSIX不支持它,不应再使用。

因此,当你的信号需要接收附加信息的时候,你必须给sa_sigaction赋信号处理函数指针,同时还要给sa_flags赋SA_SIGINFO,类似下面的代码:

 #include <signal.h>
 ……
 void sig_handler_with_arg(int sig,siginfo_t *sig_info,void *unused){……}

 int main(int argc,char **argv)
 {
          struct sigaction sig_act;
          ……
          sigemptyset(&sig_act.sa_mask);
          sig_act.sa_sigaction=sig_handler_with_arg;
          sig_act.sa_flags=SA_SIGINFO;
  
           ……
 }

如果你的应用程序只需要接收信号,而不需要接收额外信息,那你需要的设置的是sa_handler,而不是sa_sigaction,你的程序可能类似下面的代码:

 #include <signal.h>
 ……
 void sig_handler(int sig){……}

 int main(int argc,char **argv)
 {
          struct sigaction sig_act;
          ……
          sigemptyset(&sig_act.sa_mask);
          sig_act.sa_handler=sig_handler;
          
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值