Althttpd 源码分析

2023-11-01

Althttpd 源码分析 - 主函数


main 主函数

int main(int argc, char** argv)
{
	int i; 							/* 循环次数 */
	char *zPermUser = 0;			/* 守护进程的用户权限 */
	const char *zPort = 0;			/* http server端口 */
	int useChrootJail = 1;			/* 是否更改root目录 */
	struct passwd *pwd = NULL; 		/* 用户信息 */
	
	/* 记录启动时间 */
	gettimeofday(&beginTime, 0);	

	/* 转换命令行参数 */
	while( argc>1 && argv[1][0] == '-')
	{
		char *z = argv[1];						/* Key */
		char *zArg = argc>=3 ? argv[2] : "0";	/* Value */
		/* 允许参数前缀 - 或 -- */
		if(strcmp(z[0],"-") == 0 && strcmp(z[1],"-"))
			z++;
		/* 参数处理 */
		if(strcmp(z, "-user")==0)
		{
			zPermUser = zArg;		/* 守护进程的用户权限 */
		}
		else if(strcmp(z,"-root")==0)
		{
			zRoot = zArg;			/* web根目录 */
		}
		else if (strcmp(z,"-logfile") == 0)
		{
			zLogFile = zArg;		/* 日志文件路径 */
		}
		else if(strcmp(z,"-max-age") == 0)
		{
			mxAge = zArg;			/* 请求的缓存最大保持时间 */
		}
		else if(strcmp(z,"-max-cpu") == 0)
		{
			maxCpu = atoi(zArg);	/* 每个进程最大的CPU时间 */
		}
		else if(strcmp(z,"-https") == 0)
		{
			useHttps = atoi(zArg);	/* 是否使用HTTPS */
			zHttp = useHttps ? "https" : "http";
			if( useHttps )
			{
				/* 请求的源地址,初始为空 */
				zRemoteAddr = getenv("REMOTE_HOST");
			} 
		}
		else if(strcmp(z,"-port") == 0)
		{
			zPort = zArg;			/* 监听端口 */
			standalone =1;			/* 独立模式 */
		}
		else if( strcmp(z, "-family")==0 )
		{
      		if( strcmp(zArg, "ipv4")==0 )
			{
		        ipv4Only = 1;		/* 仅IPv4 */
      		}
			else if( strcmp(zArg, "ipv6")==0 )
			{
    		    ipv6Only = 1;		/* 仅IPv6 */
      		}
			else
			{
				/* 未知IP协议,打印日志 */
        		Malfunction(500, "unknown IP protocol: [%s]\n", zArg);
      		}
	    }
		else if( strcmp(z, "-jail")==0 )
		{
      		if( atoi(zArg)==0 )
			{
        		useChrootJail = 0;	/* 更改root目录,即重新指定/位置 */
      		}
    	}
		else if( strcmp(z, "-debug")==0 )
		{
      		if( atoi(zArg) )
			{
        		useTimeout = 0;		/* 调试模式,是否设置超时 */
      		}
    	}
		else if( strcmp(z, "-input")==0 )
		{
      		if( freopen(zArg, "rb", stdin)==0 || stdin==0 )
			{
				/* 输入文件无法打开 */
        		Malfunction(501, "cannot open --input file \"%s\"\n", zArg);
      		}
    	}
		else if( strcmp(z, "-datetest")==0 )
		{
	    	TestParseRfc822Date();	/* RFC822格式时间戳测试 */
      		exit(0);
   		}
		else
		{
			/* 未知参数 */
     		Malfunction(510, "unknown argument: [%s]\n", z);
    	}
		argv += 2;
		argc -= 2;
 	}

	/* 强制参数检查 */
	if(zRoot == 0)
	{
		if(standalone)
		{
			zRoot = ".";
		}
		else
		{
			Malfunction(520, "no --root specified");
		}
	}

	/* 切换目录 */
	if( chdir(zRoot)!=0 )
	{
    	Malfunction(530,"cannot change to directory [%s]", zRoot);
  	}

	/* 获取用户信息 from /etc/passwd */
	if( zPermUser )
	{
		pwd = getpwnam(zPermUser);
	} 

	/* 基于安全因素,更改当前目录为/根目录 */
	if( zPermUser && useChrootJail && getuid()==0 )
	{
    	if( chroot(".")<0 )
		{
      		Malfunction(540, /* LOG: chroot() failed */
                  "unable to create chroot jail");
    	}
		else
		{
      		zRoot = "";
    	}
  	}

	/* 启动http服务器*/
  	if( zPort && http_server(zPort, 0) )
	{
    	Malfunction(550, "failed to start server");
  	}
    
	/**************************************************************/
	/* http server有连接请求后,fork子进程,子进程退出http server监听 */
	/**************************************************************/

/* 限制进程的资源使用,避免过度导致系统崩溃 */
#ifdef RLIMIT_CPU
  if( maxCpu>0 ){
    struct rlimit rlim;
    rlim.rlim_cur = maxCpu;
    rlim.rlim_max = maxCpu;
    setrlimit(RLIMIT_CPU, &rlim);
  }
#endif

	/* 设置用户身份属性,限制资源权限 */
	if (zPermUser)
	{
		if (pwd)
		{
			if (setgid(pwd->pw_gid))
			{
				Malfunction(560, "cannot set group-id to %d", pwd->pw_gid);
			}
			if (setuid(pwd->pw_uid))
			{
				Malfunction(570, "cannot set user-id to %d", pwd->pw_uid);
			}
		}
		else
		{
			Malfunction(580, "no such user [%s]", zPermUser);
		}
	}
	if (getuid() == 0)
	{
		Malfunction(590, "cannot run as root");
	}

	/* 获取请求的地址信息,主要用于打印日志 */
	if (zRemoteAddr == 0)
	{
		address remoteAddr;
		unsigned int size = sizeof(remoteAddr);
		char zHost[NI_MAXHOST];
		if (getpeername(0, &remoteAddr.sa, &size) >= 0)
		{
			getnameinfo(&remoteAddr.sa, size, zHost, sizeof(zHost), 0, 0, NI_NUMERICHOST);
			zRemoteAddr = StrDup(zHost);
		}
	}
	if (zRemoteAddr != 0 && strncmp(zRemoteAddr, "::ffff:", 7) == 0 && strchr(zRemoteAddr + 7, ':') == 0 && strchr(zRemoteAddr + 7, '.') != 0)
	{
		zRemoteAddr += 7;
	}

	/* 处理请求,最大100次,每次15秒超时 */
	for (i = 0; i < 100; i++)
	{
		ProcessOneRequest(0);
	}
	ProcessOneRequest(1);
	exit(0);

	return 0;
}

http_server函数

int http_server(const char *zPort, int localOnly)
{
    int listener[20];               /* 服务器socket */
    int connection;                 /* socket连接 */
    fd_set readfds;                 /* select的文件描述符 */
    address inaddr;                 /* 远程对端地址 */
    socklen_t lenaddr;              /* inaddr的长度 */
    int child;                      /* 子进程PID */
    int nchildren = 0;              /* 子进程数 */
    struct timeval delay;           /* select超时时间 */
    int opt = -1;                   /* 设置socket复用标志 */
    struct addrinfo sHints;         /* 地址信息,用于提示 */
    struct addrinfo *pAddrs, *p;
    int rc;                         /* 结果返回码 */
    int i, n;
    int maxFd = -1;                 /* 最大文件描述符 */

    memset(&sHints, 0, sizeof(sHints));

    /* 协议族 */
    if(ipv4Only)
    {
        sHints.ai_family = PF_INET;
    }
    else if(ipv6Only)
    {
        sHints.ai_family = PF_INET6;
    }
    else
    {
        sHints.ai_family = PF_UNSPEC;
    }

    sHints.ai_socktype = SOCK_STREAM;
    sHints.ai_flags = AI_PASSIVE;
    sHints.ai_protocol = IPPROTO_IP;

    /* 获取地址信息 */
    rc = getaddrinfo(localOnly ? "localhost": 0,zPort, &sHints, &pAddrs);
    if(rc)
    {
        fprintf(stderr, "could not get addr info: %s", rc!=EAI_SYSTEM ? gai_strerror(rc) : strerror(errno));
        return 1;
    }

    /* MAX(n) = sizeof listener / sizeof int */
    for(n=0, p=pAddrs; n<(int)(sizeof(listener)/sizeof(listener[0])) && p != 0; p=p->ai_next)
    {
        listener[n] = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if(listener[n] >= 0)
        {
            /* 复用,防止异常终止导致socket无法正常使用 */
            setsockopt(listener[n], SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

/* IPv6 */        
#if defined(IPV6_V6ONLY)
      if( p->ai_family==AF_INET6 )
      {
        int v6only = 1;
        setsockopt(listener[n], IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only));
      }
#endif
            /* 绑定 */
            if(bind(listener[n], p->ai_addr, p->ai_addrlen) < 0)
            {
                printf("bind failed: %s\n", strerror(errno));
                close(listener[n]);
                continue;
            }
            /* 监听 */
            if( listen(listener[n], 20) < 0 )
            {
                printf("listen() failed: %s\n", strerror(errno));
                close(listener[n]);
                continue;
            }
            n++;
        }
    }

    if(n == 0)
    {
        fprintf(stderr, "cannot open any sockets\n");
        return 1;
    }

    while (1)
    {
        /* 连接短时间过多,子进程创建过快,减慢 */
        if(nchildren > MAX_PARALLEL)
        {
            sleep(nchildren - MAX_PARALLEL);
        }

        delay.tv_sec = 60;
        delay.tv_usec = 0;
        FD_ZERO(&readfds);

        for(i=0; i<n; i++)
        {
            assert(listener[0] >= 0 );
            FD_SET(listener[i], &readfds);  /* 添加监听fd到集合 */
            if(listener[i] > maxFd)
                maxFd = listener[i];
        }
        /* select阻塞,检测是否有请求 */
        select(maxFd+1, &readfds, 0, 0, &delay);

        for(i=0; i<n; i++)
        {
            if(FD_ISSET(listener[i], &readfds))
            {
                lenaddr = sizeof(inaddr);
                /* 建立连接 */
                connection = accept(listener[i], &inaddr.sa, &lenaddr);
                if(connection >= 0)
                {
                    /* fork子进程 */
                    child = fork();
                    if(child != 0)
                    {
                        /* 父进程统计子进程数,关闭连接,该连接由子进程负责 */
                        if(child>0)
                            nchildren++;
                        close(connection);
                    }
                    else
                    {
                        /* 子进程退出http_server,进入ProcessOneRequest处理请求 */
                        int nErr =0, fd;
                        /* 注意区分 标准fclose和系统调用close, 0 1表示输出输入流fd */
                        close(0);
                        fd = dup(connection); /* 复制 */
                        if(fd != 0)
                            nErr++;
                        close(1);
                        fd = dup(connection);
                        if(fd != 1)
                            nErr++;
                        close(connection);
                        return nErr;
                    }
                }
            }

            /* 有子进程终结 */
            while( (child = waitpid(0, 0, WNOHANG))>0 )
            {
                nchildren--;
            }
        }
    }
   /*  此处不会到达 */
    exit(1);
}

总结

  • Althttpd的主要三个函数为main函数、http_server函数和ProcessOneRequest函数。
  • http_server函数负责监听和fork子进程,子进程产生后退出http_server函数并调用ProcessOneRequest函数处理请求,处理请求中我们忽略掉cgi网关部分,就会发现其实是HTTP的组包收发。
  • Althttpd的设计思想别出心裁,对于加深linux的学习很有帮助。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Althttpd 源码分析 的相关文章

随机推荐

  • java assert关键字

    1 仅仅用于调试 发布不生效 2 两种用法 assert 布尔表达式 assert 布尔表达式 自定义输出信息 3 需要再idea中配置vm options ea boolean isSafe false assert isSafe isS
  • TensorFlow安装并在Pycharm搭建环境

    Anaconda安装 anaconda官方下载地址 https www anaconda com products individualhttps www anaconda com products individual 注意 此处要勾选第
  • Node.js——npm管理工具介绍

    概述 Npm是NodeJS包管理工具 在最新版本中Nodejs集成了npm 可以通过输入 npm v 来测试是否成功安装 如果你安装的是旧版本的 npm 可以通过 npm 命令来升级 命令如下 sudo npm install npm g
  • Java数组的学习(基础二)

    目录 友情提醒 第一章 数组的概念介绍 1 数组的概念 2 数组的初始化 数组的创建 数组的定义 第二章 数组的使用 数组添加元素的方法 数组的赋值 数组的遍历 数组之选择排序的升序 数组之冒泡排序的升序 数组的最小值 数组的反转 数组中常
  • 【SQL Server系列】_03关系数据库与关系代数

    文章目录 一 关系模型的数据结构及其形式化定义 1 关系的形式化定义及其有关概念 2 关系的性质 3 关系模式 4 关系数据库和关系数据库模式 二 关系的码和关系的完整性 1 候选码和主码 2 主码 PK 3 主属性和非主属性 4 外键 F
  • Vue 3.0 全家桶 + Vite 从零配置开发环境、生产环境

    上篇文章我们对比了 Vite 与 Webpack 的差异 接下来 准备将项目中用到的 Webpack 5 0 替换为 Vite 2 0 我们先着手从零配置开发 生产环境 文章目录 一 初始化 1 初始化 package json 2 安装
  • CWE-通用弱点枚举简介

    对于软件来说 安全是航空 航天 军工 电力 金融等关键行业极为重视的特性之一 因此 保证软件尽量安全是软件研发人员的重要责任 可以说 软件安全漏洞是软件研发者的一大死敌 古语有云 知彼知己 百战不殆 要想取得战争的胜利 就要尽可能充分认识软
  • SSL 协议分析:ClientHello 过程分析

    最近在分析某个PC端程序的登录过程 发现它用的是openssl进行https通讯的 由于以前没有openssl的使用经验 遂开始学习这个库 在这里记录一些TLS协议的原理 以及openssl实现TLS协议的代码分析 TLS 相当于 SSL
  • iOS编程基础-Swift(二)-函数

    Swift入门指南 iOS9 Programming Fundamentals With swift 第二章 函数 Swift语法中最具特色也最重要的就是声明和调用函数的方式 所有代码都位于函数中 而动作则是由函数触发的 print sum
  • 谓词逻辑中量词的符号化

    在谓词演算中 最基本的命题符号化就三种类型 主语是具体个体对象的 用谓词加括号 括号里是具体个体表示 描述所有的 任意的个体对象 用全称量词 特性谓词做蕴含前件 描述一些客体对象 用存在量词 特性谓词作合取项 注 命题的符号表达式中所有个体
  • Swagger实现登录查看Api

    1 登录页面添加Cookie 工具 js cookie Cookies set token data data token token 复制代码 2 后端添加过滤器 package filter import cn hutool core
  • MCP2515调试笔记(一)

    MSP430 MCP2515调试笔记 一 MCP是MricoChip 公司生产的一款独立CAN控制器 相比恩智浦公司的SJA1000 它的主要特点是与微控制器之间通过SPI方式进行数据交换而不是SJA1000的并行方式 这样可以大大减少引脚
  • 1.GItLab速安装搭建

    安装之前要先准备虚拟机 我得虚拟机ip是192 168 56 10 1 GItLab快速安装搭建 可参考官方安装手册 https about gitlab com install centos 7 下载相关gitlab版本 上传至虚拟机 h
  • Python爬虫入门实战3:获取CSDN个人博客信息

    老猿Python博文目录 https blog csdn net LaoYuanPython article details 98245036 一 引言 在 https blog csdn net LaoYuanPython article
  • C语言小游戏(皇帝和他的后宫)

    小游戏 皇帝和他的后宫 今天我们来玩一个C语言的小游戏 皇帝和他的后宫 所有代码我放在github上了 在文章末尾 这个游戏是仿照着老九学堂的C语言课程写得 这里面运用了二维数组的相关知识 游戏有五个状态 如下图所示 然后我们来说明一下各个
  • 【java基础核心大总结(九)】

    泛型 在 Jdk1 5 中 提出了一种新的概念 泛型 那么什么是泛型呢 泛型其实就是一种参数化的集合 它限制了你添加进集合的类型 泛型的本质就是一种参数化类型 多态也可以看作是泛型的机制 一个类继承了父类 那么就能通过它的父类找到对应的子类
  • 再谈应用环境下的TIME_WAIT和CLOSE_WAIT

    昨天解决了一个HttpClient调用错误导致的服务器异常 具体过程如下 http blog csdn net shootyou article details 6615051 里头的分析过程有提到 通过查看服务器网络状态检测到服务器有大量
  • 1-9、Lua协同程序

    1 9 Lua协同程序 文章目录 1 9 Lua协同程序 1 协同的基础 2 管道和过滤器 3 用作迭代器的协同 4 非抢占式多线程 协同程序 coroutine 与多线程情况下的线程比较类似 有自己的堆栈 自己的局部变量 有自己的指令指针
  • java基础快速入门--面向对象(基础)

    类与对象 看一个养猫问题 张老太养了两只猫 一只名字叫小白 今年三岁 白色 还有一只叫小花 今年一百岁 花色 请编写一个程序 当用户输入小猫的名字时 就显示该猫的名字 年龄 颜色 如果用户输入的小猫名错误 则显示张老太没有这只猫猫 使用现有
  • Althttpd 源码分析

    Althttpd 源码分析 主函数 main 主函数 http server函数 总结 main 主函数 int main int argc char argv int i 循环次数 char zPermUser 0 守护进程的用户权限 c