http server 简单实现

本blog主要是模仿http server的实现,使得客户端使用浏览器访问server所指定的目录。

  • 当访问的为一个目录时, 则列出改目录下的所有文件
  • 当访问的是文件时,则下载文件到本地

本log仅仅做为httpd server的测试版本,功能不健全,学习而已!

1. 效果图

1.1 整体图:

mark

1.2 目录:

要查看,不能直接点击,要在搜索栏中输入绝对路径,如 http://192.168.58.128/log/
mark

1.3 文件:

要下载文件,不能直接点击,要在搜索栏中输入绝对路径,如 http://192.168.58.128/log/syslog
mark

1.4 错误:

mark

2. 预备知识

欲学会本log所述内容,必须具备一下的基础知识:

  • 守候进程:

基本概念:https://www.cnblogs.com/z-sm/p/5675051.html
编程API:https://blog.csdn.net/lianghe_work/article/details/47659889

  • sscanf()函数:

函数详解:http://www.cnblogs.com/Jimmy1988/p/8900440.html

  • 日志操作:

概念及操作:http://www.cnblogs.com/Jimmy1988/p/8892483.html

  • **socket 编程: **

基础知识:http://www.cnblogs.com/Jimmy1988/p/7839940.html
socket API: http://www.cnblogs.com/Jimmy1988/p/7895213.html

3. 源码地址

本blog的源码放到了本人的github上了,包括项目源码/makefile/配置文件等。

github 地址如下:

https://github.com/Jimmy-Nie/httpd-server-.git

4. 源码展示

本文源码分为三个文件:

  • deamon.c: 各种函数功能的实现
  • http.c: main函数
  • http.h: 头文件

4.1 源码

①. http.h

/**********************************************************************************************
*Copyright (C), 2018 ,Jimmy_Nie.  https://www.cnblogs.com/Jimmy1988/
*
*
*File name: httpd.c
*Description:实现httpd功能
* Author		Date		Modify 
* Jimmy			2018-04-16	Create
*
**********************************************************************************************/
#ifndef HTTP_H
#define HTTP_H

#include <arpa/inet.h>
#include <dirent.h>

#include <errno.h>
#include <fcntl.h>
#include <linux/if.h>
#include <netinet/in.h>

#include <pwd.h>
#include <grp.h>

#include <stdio.h>
#include <sys/socket.h>
#include <syslog.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdarg.h>

#include <time.h>
#include <unistd.h>

/*********************************全局变量***************************************/
extern char home_dir[32];
extern char ip_addr[16];
extern char port_no[8];
extern char backlog[8];

#define _BSD_SOURCE

/***********************************************************
 * Function: write_info
 * Input:
 * Output: 
 * Return: 
 * Autor		Date			Modify
 * Jimmy		2018-04-16		Create
 * 
 ***********************************************************/
#define write_info(fmt, arg...)	\
{														\
	char buff[512]; char buff1[256];					\
	bzero(buff, sizeof(buff));							\
	bzero(buff1, sizeof(buff));							\
	sprintf(buff, "<%s:%d> ", __FUNCTION__, __LINE__);	\
	sprintf(buff1, fmt, ##arg);							\
	strcat(buff, buff1);								\
	syslog(LOG_INFO, "%s", buff);						\
}


/*********************************函数声明***************************************/
extern void init_daemon(char *p_name, int facility);
extern  int init_socket(int *p_sockfd);
extern int get_ip_addr(char *ip_addr);
extern void response_client(int client_sock, char *path);
extern int read_conf(char *cmd, char *data);


#endif

②. http.c

/**********************************************************************************************
*Copyright (C), 2018 ,Jimmy_Nie.  https://www.cnblogs.com/Jimmy1988/
*
*
*File name: httpd.c
*Description:实现httpd功能
* Author		Date		Modify 
* Jimmy			2018-04-16	Create
*
**********************************************************************************************/
#include "http.h"

char home_dir[32];
char ip_addr[16];
char port_no[8];
char backlog[8];

/***********************************************************
 * Function: main函数
 * Input:
 * Output: 
 * Return: 
 * Autor		Date			Modify
 * Jimmy		2018-04-16		Create
 * 
 ***********************************************************/
int main(int argc, char *argv[])
{
	int rtn = 0;
	int sock_fd = -1;
	struct sockaddr_in client_addr;
	char buf[256];
	pid_t pid = -1;
	
	//1. 以守候进程的方式运行此http进程
	init_daemon(argv[0], LOG_INFO);

	//2. 获取参数值
	//2.1 获取home_dir, 如果配置文件没有,则默认为/tmp
	rtn = read_conf("home_dir", home_dir);
	if(rtn < 0)
		exit(EXIT_FAILURE);
	else if(0 == rtn)	//即未从conf文件中读取到数据
	{
		bzero(home_dir, sizeof(home_dir));
		strcpy(home_dir, "/tmp");
	}

	//2.2 获取ip_addr, 如果配置文件没有,则设置为本机的wlan0的ip地址
	rtn = read_conf("ip_addr", ip_addr);
	if(rtn < 0)
		exit(EXIT_FAILURE);
	else if(0 == rtn)	//即未从conf文件中读取到数据
	{
		bzero(ip_addr, sizeof(ip_addr));
		get_ip_addr(ip_addr);
	}

	//2.3 获取port_no, 如果配置文件没有,则默认为80
	rtn = read_conf("port_no", port_no);
	if(rtn < 0)
		exit(EXIT_FAILURE);
	else if(0 == rtn)	//即未从conf文件中读取到数据
	{
		bzero(port_no, sizeof(port_no));
		strcpy(port_no, "80");
	}

	//2.4 获取port_no, 如果配置文件没有,则默认为10(最多链接10个client)
	rtn = read_conf("backlog", backlog);
	if(rtn < 0)
		exit(EXIT_FAILURE);
	else if(0 == rtn)	//即未从conf文件中读取到数据
	{
		bzero(backlog, sizeof(backlog));
		strcpy(backlog, "10");
	}

	write_info("home_dir=%s, ip_addr=%s, port_no=%d, backlog=%d\n", home_dir, ip_addr, atoi(port_no), atoi(backlog));
	
	//3. 初始化socket
	rtn = init_socket(&sock_fd);
	if(rtn < 0)
		exit(EXIT_FAILURE);

	//4. 接收新的client链接
	while(1)
	{
		int len;
		int new_fd = -1;

		len = sizeof(struct sockaddr_in);;
		memset(&client_addr, 0, len);
		new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &len);

		if(new_fd < 0)
		{
			write_info("accept new client connection failed !! [%d:%s] \n", errno, strerror(errno));	
			exit(EXIT_FAILURE);
		}

		bzero(buf, sizeof(buf));
		sprintf(buf, "Connect from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
		write_info("%s\n", buf);

		if((pid = fork()) == -1)
		{
			write_info("fork() failed");
			exit(EXIT_FAILURE);
		}
		
		else if(0 == pid)	//在子进程中
		{
			bzero(buf, sizeof(buf));
			len = recv(new_fd, buf, sizeof(buf), 0);
			if(len > 0)		//接到了信息
			{
				char req[256];
				bzero(req, sizeof(req));

				sscanf(buf, "GET %s HTTP", req);
				bzero(buf, sizeof(buf));
				sprintf(buf, "Request get the file: %s", req);
				write_info("%s\n", buf);
				response_client(new_fd, req);
			}
			sleep(30);	//建议使用chrome,firefox和IE貌似一直在请求,一旦tcp链接断开,就GG了
			exit(EXIT_SUCCESS);
		}
		
		else 	//在父进程中
		{
			close(new_fd);
			continue;
		}
	}

	close(sock_fd);
	return 0;
}

③. deamon.c

/**********************************************************************************************
*Copyright (C), 2018 ,Jimmy_Nie.  https://www.cnblogs.com/Jimmy1988/
*
*
*File name: httpd.c
*Description:实现httpd功能
* Author		Date		Modify 
* Jimmy			2018-04-16	Create
*
**********************************************************************************************/
#include "http.h"

/***********************************************************
 * Function: init_daemon
 * Input: 
 	1. char *p_name: 进程名称
 	2. int facility: 系统log进程的level
 * Output: 
 * Return: 
 * Autor		Date			Modify
 * Jimmy		2018-04-16		Create
 * 
 ***********************************************************/
void init_daemon(char *p_name, int facility)
{
	int pid = 0;
	int cnt = 0;

	//1. 忽略所有可能的终端信号(守候进程不能受到终端的影响)
	signal(SIGINT, SIG_IGN);	//终端中断符
	signal(SIGTTOU, SIG_IGN);	//后台向控制端tty写作业
	signal(SIGTSTP, SIG_IGN);	//终端挂起
	signal(SIGHUP, SIG_IGN);	//链接断开
	signal(SIGQUIT, SIG_IGN);	//终端退出

	//2. 创建子进程,父进程推出(因为父进程由终端创建)
	if((pid = fork()) > 0)		//在父进程中
		exit(EXIT_SUCCESS);
	else if(pid < 0)
	{
		printf("fork error: [%d:%s]", errno, strerror(errno));
		exit(EXIT_FAILURE);
	}

	//3. 设置新的会话组长,新进程组长,脱离终端
	setsid();

	//4. 再次创建一个子进程,并让现在的父进程退出
	if((pid = fork()) > 0)		//在父进程中
		exit(EXIT_SUCCESS);
	else if(pid < 0)
	{
		printf("fork error: [%d:%s]", errno, strerror(errno));
		exit(EXIT_FAILURE);
	}

	//5. 修改子进程的主目录为/tmp
	chdir("/tmp");

	//6. 重置文件掩码
	umask(0);

	//7. 关闭所有父进程打开的文件描述符(包括stdin/stdout/stderr这三个; NOFILE为256)
	for(cnt=0; cnt<NOFILE; cnt++)
		close(cnt);

	//8. 忽略子进程的退出信号
	signal(SIGCHLD, SIG_IGN);

	//9. 与syslogd守候进程关联
	openlog(p_name, LOG_PID, facility);	//每个message都会增加pid进去
	
	return ;
}



/***********************************************************
 * Function: read_conf
 * Description: 读取配置文件,找出命令对应的值
 * Input:
 	1. char *cmd: 欲查找的命令
 * Output: 
 	1. char *data: 查找到的命令的值
 * Return: 
 	1. 命令所在字符串中的位置
 *
 * Autor		Date			Modify
 * Jimmy		2018-04-16		Create
 * 
 ***********************************************************/
int read_conf(char *cmd, char *data)
{
	int fd = -2;
	char buf[1024];
	size_t rd_size;
	char *match = NULL;
	
	memset(buf, 0, sizeof(buf));

	//0. 检查传入的参数
	if((NULL == cmd) || (NULL == data))
	{
		write_info("Input arguments error !");
		return -1;
	}
	
	//1. 打开配置文件
	fd = open("/etc/httpd_test.conf", O_RDONLY);
	if(fd < 0)	//注意,此处fd=0,因为关闭了stdin/stdout/stderr,所以fd应该从0开始 (TMD,排查了很久,以为出错了)
	{
		write_info("open the file /etc/httpd_test.conf failed! [fd=%d] [%d:%s]", fd, errno, strerror(errno));
		return -1;
	}

	//2. 读取文件的所有数据
	rd_size = read(fd, buf, sizeof(buf));
	if((rd_size <= 0) || (rd_size == sizeof(buf)))
	{
		write_info("read the file /etc/httpd_test.conf failed! [%d:%s]", errno, strerror(errno));
		return -1;
	}
	buf[rd_size] = '\0';
	
	//3. 关闭文件
	close(fd);

	//4. 参数匹配
	if(strncmp(cmd, "home_dir", strlen("home_dir")) == 0)
	{
		match = strstr(buf, "home_dir=");
		if(NULL == match)		//若未找到匹配项
		{
			write_info("Does not found the cmd[%s] in the file /etc/httpd_test.conf!", cmd);
			return 0;
		}

		rd_size = sscanf(match, "home_dir=%s", data);
		write_info("Found the command [%s], position=%ld\n", cmd, rd_size);
		return rd_size;
	}

	if(strncmp(cmd, "port_no", strlen("port_no")) == 0)
	{
		match = strstr(buf, "port_no=");
		if(NULL == match)		//若未找到匹配项
		{
			write_info("Does not found the cmd[%s] in the file /etc/httpd_test.conf!", cmd);
			return 0;
		}

		rd_size = sscanf(match, "port_no=%s", data);
		write_info("Found the command [%s], position=%ld\n", cmd, rd_size);
		return rd_size;
	}

	if(strncmp(cmd, "ip_addr", strlen("ip_addr")) == 0)
	{
		match = strstr(buf, "ip_addr=");
		if(NULL == match)		//若未找到匹配项
		{
			write_info("Does not found the cmd[%s] in the file /etc/httpd_test.conf!", cmd);
			return 0;
		}

		rd_size = sscanf(match, "ip_addr=%s", data);
		write_info("Found the command [%s], position=%ld\n", cmd, rd_size);
		return rd_size;
	}

	if(strncmp(cmd, "backlog", strlen("backlog")) == 0)
	{
		match = strstr(buf, "backlog=");
		if(NULL == match)		//若未找到匹配项
		{
			write_info("Does not found the cmd[%s] in the file /etc/httpd_test.conf!", cmd);
			return 0;
		}

		rd_size = sscanf(match, "backlog=%s", data);
		write_info("Found the command [%s], position=%ld\n", cmd, rd_size);
		return rd_size;
	}
	
	return 0;
}

/***********************************************************
 * Function: init_socket
 * Description: 初始化socket
 * Input:
 	1. char *p_sockfd: socket 文件描述符
 * Output: 
 * Return: 
 	0: on success
 	1:	on failure
 *
 * Autor		Date			Modify
 * Jimmy		2018-04-16		Create
 * 
 ***********************************************************/
 int init_socket(int *p_sockfd)
{
	int rtn =0;
	struct sockaddr_in  serv_addr;
	int sock_fd;
	
	//1. 建立socket
	sock_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(0 > sock_fd)
	{
		write_info("Create socket failed !! [%d:%s] ", errno, strerror(errno));
		return -1;
	}
	write_info("socket()");
	
	//2. 设置socket允许复用本地IP和端口
	int tmp = 1;
	setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp));

	//3. 绑定本地IP和端口
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(atoi(port_no));
	serv_addr.sin_addr.s_addr = inet_addr(ip_addr);

	rtn = bind(sock_fd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in));
	if(rtn < 0)
	{
		write_info("Bind the IP addr [%s] failed !! [%d:%s] ", errno, ip_addr, strerror(errno));
		return -1;
	}
	write_info("bind()");
	
	//4. 监听网络
	rtn = listen(sock_fd, atoi(backlog));
	if(rtn < 0)
	{
		write_info("Listen the network failed !! [%d:%s] ", errno, strerror(errno));
		return -1;
	}
	write_info("listen()");

	*p_sockfd = sock_fd;
	
	return rtn;
}

 /***********************************************************
 * Function: get_ip_addr
 * Description: 初始化socket
 * Input:
 	1. char *p_addr: 本地IP地址
 	2. int port: 本地端口
 	3. int backlog: 最多可接受多少个链接
 * Output: 
 * Return: 
 	0: on success
 	1:	on failure
 *
 * Autor		Date			Modify
 * Jimmy		2018-04-16		Create
 * 
 ***********************************************************/
int get_ip_addr(char *ip_addr)
{
	int sock_fd; 
	struct ifreq ifr;

	sock_fd = socket(AF_INET, SOCK_DGRAM, 0);

	strcpy(ifr.ifr_name, "ens33");		//注意此处的修改,不同的系统名称不一样,一般为eth0

	//获取ens33的接口信息
	if(ioctl(sock_fd, SIOCGIFADDR, &ifr) < 0)
	{
		write_info("bind");
		return -1;
	}

	sprintf(ip_addr, "%s", inet_ntoa(((struct sockaddr_in *)&(ifr.ifr_addr))->sin_addr));

	return 0;
}

/***********************************************************
* Function: file_type
* Description: 获取文件的类型
* Input:
   1. mode_t st_mode: 文件类型
* Output: 
* Return: 
	文件类型的标识符
*
* Autor 	   Date 		   Modify
* Jimmy 	   2018-04-17	   Create
* 
***********************************************************/
char file_type(mode_t st_mode)
{
	/*	only support c89
	static char type;
	switch(st_mode & S_IFMT)
	{
		case S_IFSOCK:
			type = 's';
			break;
		case S_IFLNK:
			type = 'l';
			break;
		case S_IFREG:
			type = '-';
			break;
		case S_IFBLK:
			type = 'b';
			break;
		case S_IFCHR:
			type = 'c';
			break;
		case S_IFIFO:
			type = 'p';
			break;
		case S_IFDIR:
			type = 'd';
			break;
		default:
			type = 'E';
			break;
	}

	return type;
	*/

	if (S_ISREG(st_mode))
		return '-';
	else if (S_ISDIR(st_mode))
		return 'd';
	else if (S_ISCHR(st_mode))
		return 'c';
	else if (S_ISBLK(st_mode))
		return 'b';
	else if (S_ISLNK(st_mode))
		return 'l';
	else if (S_ISFIFO(st_mode))
		return 'p';
	//else if (S_ISSOCK(st_mode))
		//return 's';
	else
		return '?';
	
}

/***********************************************************
* Function: dir_up
* Description: 获取上级目录
* Input:
   1. char *dir_path: 目录
* Output: 
* Return: 
	文件类型的标识符
*
* Autor 	   Date 		   Modify
* Jimmy 	   2018-04-17	   Create
* 
***********************************************************/
char *dir_up(char *dir_path)
{
	static char path[128];
	int len;

	strcpy(path, dir_path);
	len = strlen(path);

	if((len > 1) && (path[len-1] == '/'))
		len--;
	while((len > 1) && (path[len-1] != '/'))
		len--;
	
	path[len] = '\0';
	return path;
}

/***********************************************************
* Function: response_client
* Description: 响应客户端的请求:若为dir,则列出所有文件;若为file,则下载
* Input:
   1. char *p_addr: 本地IP地址
   2. char *path:	文件的请求位置
* Output: 
* Return: 
   0: on success
   1:  on failure
*
* Autor 	   Date 		   Modify
* Jimmy 	   2018-04-17	   Create
* 
***********************************************************/
void response_client(int client_sock, char *path)
{
	int rtn = 0;
	int len = 0;
	char real_path[64];
	char file_name[128];
	char tmp_port[8];
	struct stat stat_info;
	char send_msg[512];
	DIR *dir = NULL;
	int fd = -1;
	struct dirent *dirt;
	ssize_t send_size = 0;
	char *p_time ;
	struct passwd *p_user;
	struct group *p_group;
	
	//0. 初始化变量
	memset(real_path, 0, sizeof(real_path));
	memset(tmp_port, 0, sizeof(tmp_port));
	memset(send_msg, 0, sizeof(send_msg));
	memset(file_name, 0, sizeof(file_name));
	//memset(p_time, 0, sizeof(p_time));
	
	//1. 获取绝对路径
	sprintf(real_path, "%s%s", home_dir, path);
	
	write_info("The real_path: %s\n", real_path);
	
	//2. 获取端口信息(用于后续的输出)
	//sprintf(tmp_port, " %s", port_no);

	//3. 获取绝对路径的文件属性(文件还是目录,或其它)
	rtn = stat(real_path, &stat_info);
	if(0 != rtn)
	{
		write_info("Get the path[%s] state failed!! [%d:%s] ", real_path, errno, strerror(errno));
		//将错误信息发送给client端
		sprintf(send_msg, "HTTP/1.1 200 OK\r\nServer: Http test server\r\nConnection: close\r\n\r\n"	\
							"<html><head><title>%d - %s</title></head>"									\
							"<body><font size=+4>Linux http server</font><br><hr width=\"100%%\"><br><center>"	\
							"<table border cols=3 width=\"100%%\"></table><font_color=\"C0000\" size=+2>"		\
							"connect to administrator, error code is \n%s %s </font></body></html>",
							errno, strerror(errno), path, strerror(errno));

		send(client_sock, send_msg, strlen(send_msg)+1, 0);
		if(send_size <= 0)
		{
			write_info("Send http header failed !!! \n");
		}
		else
		{
			write_info("Send http header succeed ! \n");
		}
		
		return -1;
	}

	//如果请求的路径是一个文件,则将改文件直接发送给客户端
	else if(S_ISREG(stat_info.st_mode))
	{
		write_info("The %s is a file! \n", real_path);
		
		fd = open(real_path, O_RDONLY);		//只读的方式打开
		len = lseek(fd, 0, SEEK_END);		//查看打开文件的大小
		lseek(fd, 0, SEEK_SET);				//将文件读写定位到开始位置

		//开辟一个内存空间,用于存放读取到的文件信息
		char *tmp_mem = (char *)malloc(len+1);
		bzero(tmp_mem, len+1);

		for(int i=0; i<len; i+=1024)	//读取文件,每次读 1k bytes
		{
			rtn = read(fd, tmp_mem+i, 1024);
			if(rtn <= 0)
				write_info("Read the file [%s] failed!! [%d:%s] ", real_path, errno, strerror(errno));
		}

		close(fd);		//读完之后,就可以关闭文件了

		//将文件内容发送给客户端
		memset(send_msg, 0, sizeof(send_msg));
		sprintf(send_msg, "HTTP/1.1 200 OK\r\nServer: Http test server\r\nConnection keep alive\r\n"\
							"Content-type: application/*\r\nContent-length:%d\r\n\r\n", len);
		send_size = send(client_sock, send_msg, strlen(send_msg)+1, 0);
		if(send_size <= 0)
		{
			write_info("Send http header failed !!! \n");
		}
		else
		{
			write_info("Send http header succeed ! \n");
		}

		send(client_sock, tmp_mem, len, 0);
		if(send_size <= 0)
		{
			write_info("Send http message failed !!! \n");
		}
		else
		{
			write_info("Send http message succeed, len=%d ! \n", len);
		}

		sleep(20);
		free(tmp_mem);
	}
	
	//如果请求的路径是一个目录,将目录下的列表信息全部输出
	else if(S_ISDIR(stat_info.st_mode))
	{
		write_info("The %s is a directory! \n", real_path);
		
		//将http协议信息发送给客户端,为了便于显示,先发送表格头
		memset(send_msg, 0, sizeof(send_msg));
		sprintf(send_msg, "HTTP/1.1 200 OK\r\nServer: Http test server\r\nConnection: close\r\n\r\n"	\
							"<html><head><title>Jimmy_Nie: %s</title></head>"									\
							"<body><font size=+4>Linux http server file - Jimmy</font><br><hr width=\"100%%\"><br><center>"	\
							"<table border cols=3 width=\"100%%\">"							\
							"<caption><font size=+3> Directory: %s</font></caption>\n"		\
							"<tr><td>Name</td><td>Type</td><td>Owner</td><td>Group</td>"	\
							"<td>Size</td><td>Modify time</td></tr>\n",						\
							real_path, real_path);

		send_size = send(client_sock, send_msg, strlen(send_msg)+1, 0);
		if(send_size <= 0)
		{
			write_info("Send http header failed !!! \n");
		}
		else
		{
			write_info("Send http header succeed ! \n");
		}

		dir = opendir(real_path);	//打开绝对路径的目录
		if(NULL == dir)		//若打开目录失败
		{
			write_info("Open the %s failed! \n", real_path);
			memset(send_msg, 0, sizeof(send_msg));
			sprintf(send_msg, "</table><font color=\"CC0000\" size=+2>%s</font></body></html>", strerror(errno));

			send_size = send(client_sock, send_msg, strlen(send_msg)+1, 0);
			if(send_size <= 0)
			{
				write_info("Send http header failed !!! \n");
			}
			else
			{
				write_info("Send http header succeed ! \n");
			}
			
			return -1;
		}

		while((dirt = readdir(dir)) != NULL)
		{
			if(dirt->d_name[0] == '.')	//即为隐藏文件
				continue;
			
			send(client_sock, "<tr>", strlen("<tr>"), 0);
			bzero(file_name, sizeof(file_name));
			sprintf(file_name, "%s/%s", real_path, dirt->d_name);

			write_info("opened the file %s !\n", file_name);
			
			if(stat(file_name, &stat_info) == 0)	//读取文件信息
			{
				memset(send_msg, 0, sizeof(send_msg));
				if(0 == strcmp(dirt->d_name, ".."))		//若为..,则要求到达上级目录
					sprintf(send_msg, "<td><a href=\"http://%s:%s %s\">(parent)</a></td>",
										ip_addr, port_no, dir_up(path));
				else
					sprintf(send_msg, "<td><a href=\"http://%s:%s %s/%s\">%s</a></td>",
										ip_addr, port_no, path, dirt->d_name, dirt->d_name);

				send_size = send(client_sock, send_msg, strlen(send_msg), 0);
				if(send_size <= 0)
				{
					write_info("Send http header failed !!! \n");
				}
				else
				{
					write_info("Send http header succeed ! \n");
				}

				p_time = ctime(&stat_info.st_mtime);		//获取修改时间
				p_user = getpwuid(stat_info.st_uid);		//获取文件拥有着
				p_group = getgrgid(stat_info.st_gid);		//获取文件组信息

				write_info("Time: %s, user: %s, group: %s\n", p_time, p_user->pw_name, p_group->gr_name);
				
				//向客户端输出文件类型
				memset(send_msg, 0, sizeof(send_msg));
				sprintf(send_msg, "<td>%c</td>", file_type(stat_info.st_mode));
				send_size = send(client_sock, send_msg, strlen(send_msg), 0);
				if(send_size <= 0)
				{
					write_info("Send file type failed !!! \n");
				}
				else
				{
					write_info("Send file type succeed ! \n");
				}

				//向客户端输出文件所有者信息
				memset(send_msg, 0, sizeof(send_msg));
				sprintf(send_msg, "<td>%s</td>", p_user->pw_name);
				send_size = send(client_sock, send_msg, strlen(send_msg), 0);
				if(send_size <= 0)
				{
					write_info("Send file user failed !!! \n");
				}
				else
				{
					write_info("Send file user succeed ! \n");
				}
				
				//向客户端输出文件组信息
				memset(send_msg, 0, sizeof(send_msg));
				sprintf(send_msg, "<td>%s</td>", p_group->gr_name);
				send_size = send(client_sock, send_msg, strlen(send_msg), 0);
				if(send_size <= 0)
				{
					write_info("Send file group failed !!! \n");
				}
				else
				{
					write_info("Send file group succeed ! \n");
				}
				
				//向客户端输出文件大小
				memset(send_msg, 0, sizeof(send_msg));
				sprintf(send_msg, "<td>%d</td>", stat_info.st_size);
				send_size = send(client_sock, send_msg, strlen(send_msg), 0);
				if(send_size <= 0)
				{
					write_info("Send file size failed !!! \n");
				}
				else
				{
					write_info("Send file size succeed ! \n");
				}
				
				//向客户端输出修改时间
				char tmp_send[512];
				memset(send_msg, 0, sizeof(send_msg));		//此处很奇怪,用send_message就发不出去
				memset(tmp_send, 0, sizeof(tmp_send));
				sprintf(tmp_send, "<td>%s</td>", p_time);
								
				send_size = send(client_sock, tmp_send, strlen(tmp_send), 0);
				if(send_size <= 0)
				{
					write_info("Send file modify time failed !!! \n");
				}
				else
				{
					write_info("Send file modify time succeed ! \n");
				}
			}
			
			send(client_sock, "</tr>\n", strlen("</tr>\n"), 0);
		}
		
		send(client_sock, "</table></center></body></html>", strlen("</table></center></body></html>"), 0);
	}

	else	//如果木有权限访问
	{
		memset(send_msg, 0, sizeof(send_msg));
		sprintf(send_msg, "HTTP/1.1 200 OK\r\nServer: Http test server\r\nConnection: close\r\n\r\n"	\
							"<html><head><title>%s</title></head>"									\
							"<body><font size=+4>Linux http server file</font><br><hr width=\"100%%\"><br><center>"	\
							"<table border cols=3 width=\"100%%\"></table>"							\
							"<font color=\"CC0000\" size=+2>[%s]Permission denied, please contact the admin"\
							"</font></body></html>",	path);

		send_size = send(client_sock, send_msg, strlen(send_msg)+1, 0);
		if(send_size <= 0)
		{
			write_info("Send http header failed !!! \n");
		}
		else
		{
			write_info("Send http header succeed ! \n");
		}
	}
	
	return ;
}


4.2 makefile

#==========================================
# Copyright @ Jimmy 2018-04-21
# httpd makefile
# e-mail: JimmyNie2017@163.com
#==========================================

SHELL = /bin/bash

SRC_DIR = ./code
INC_DIR = ./code

SRC_FILE := $(shell find $(SRC_DIR) -name '*.c')
SRC_FILE += $(shell find $(SRC_DIR) -name '*.cpp')

INC_FILE := $(shell find $(INC_DIR) -name '*.h')
INC_FILE += $(shell find $(INC_DIR) -name '*.hpp')

TEMP_INC_DIR := $(dir $(INC_FILE))
INC_DIR := $(foreach tmp, $(TEMP_INC_DIR), -I$(tmp))

OBJS = $(patsubst %.c, %.o, $(patsubst %.cpp, %.o, $(SRC_FILE)))

# Target with the date time
TARGET := httpd-$(shell date --rfc-3339='date')

#compile tools
CC 	:= gcc
CXX := g++

#libs as
LIBS := -lpthread
LIBS +=

#compile flags 
CFLAGS := -w -Wall -g -O0 
CFLAGS += $(INC_DIR)
CXXFLAGS := -std=c++0x $(CFLAGS)
CFLAGS += -std=c99 

#=========================================================
all: $(TARGET)

SEE:
	@echo -e "SRC_FILE: $(SRC_FILE)"
	@echo -e "OBJS: $(OBJS)"
$(TARGET):$(OBJS) 
	@$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
	@echo  -e "\033[31m\033[1m make $@ done. \033[0m";	#red

%.o:%.c
	@$(CC) $(CFLAGS) -c $^ -o $@
	@echo -e "\033[32m\033[1m make $@ done. \033[0m";	#green

%.o:%.cpp
	@$(CXX) $(CXXFLAGS) -c $^ -o $@
	@echo -e "\033[32m\033[1m make $@ done. \033[0m";#green

clean:
	@rm -f $(TARGET])
	@echo  -e "\033[31m\033[1m remove $(TARGET) done. \033[0m";	#red

cleanall:
	@rm -f $(TARGET)
	@rm -f $(OBJS)
	@echo  -e "\033[31m\033[1m Removed $(TARGET) done. \033[0m";	#red
	@echo -e "\033[32m\033[1m Removed: $(OBJS) done. \033[0m";		#yellow

install:
	@$(shell rm -f /usr/bin/httpd-2018*)
	@echo -e "Removed the old version"
	@$(shell cp $(TARGET) /usr/bin/$(TARGET))
	@$(shell cp ./httpd_test.conf /etc/httpd_test.conf)
	@echo -e "\033[32m\033[1m copy $(TARGET) to /usr/bin/$(TARGET) \033[0m";	#green
	@echo -e "\033[32m\033[1m copy httpd_test.conf to /etc/httpd_test.conf \033[0m";		#green


4.3 配置文件

#--------------------------------------------
# Copyright @ 2018-04-17
# This is httpd configure file
# Autor: JImmy_Nie
#--------------------------------------------

home_dir=/var
ip_addr         #use the local ip address
port_no=80
backlog=8

4.4 一些结构体定义

#include <linux/if.h>

/*
 * Interface request structure used for socket
 * ioctl's.  All interface ioctl's must have parameter
 * definitions which begin with ifr_name.  The
 * remainder may be interface specific.
 */

/* for compatibility with glibc net/if.h */
#if __UAPI_DEF_IF_IFREQ
struct ifreq {
#define IFHWADDRLEN     6
        union
        {
                char    ifrn_name[IFNAMSIZ];            /* if name, e.g. "en0" */
        } ifr_ifrn;

        union {
                struct  sockaddr ifru_addr;
                struct  sockaddr ifru_dstaddr;
                struct  sockaddr ifru_broadaddr;
                struct  sockaddr ifru_netmask;
                struct  sockaddr ifru_hwaddr;
                short   ifru_flags;
                int     ifru_ivalue;
                int     ifru_mtu;
                struct  ifmap ifru_map;
                char    ifru_slave[IFNAMSIZ];   /* Just fits the size */
                char    ifru_newname[IFNAMSIZ];
                void *  ifru_data;
                struct  if_settings ifru_settings;
        } ifr_ifru;
};
#endif /* __UAPI_DEF_IF_IFREQ */

#define ifr_name        ifr_ifrn.ifrn_name      /* interface name       */
#define ifr_hwaddr      ifr_ifru.ifru_hwaddr    /* MAC address          */
#define ifr_addr        ifr_ifru.ifru_addr      /* address              */
#define ifr_dstaddr     ifr_ifru.ifru_dstaddr   /* other end of p-p lnk */
#define ifr_broadaddr   ifr_ifru.ifru_broadaddr /* broadcast address    */
#define ifr_netmask     ifr_ifru.ifru_netmask   /* interface net mask   */
#define ifr_flags       ifr_ifru.ifru_flags     /* flags                */
#define ifr_metric      ifr_ifru.ifru_ivalue    /* metric               */
#define ifr_mtu         ifr_ifru.ifru_mtu       /* mtu                  */
#define ifr_map         ifr_ifru.ifru_map       /* device map           */
#define ifr_slave       ifr_ifru.ifru_slave     /* slave device         */
#define ifr_data        ifr_ifru.ifru_data      /* for use by interface */
#define ifr_ifindex     ifr_ifru.ifru_ivalue    /* interface index      */
#define ifr_bandwidth   ifr_ifru.ifru_ivalue    /* link bandwidth       */
#define ifr_qlen        ifr_ifru.ifru_ivalue    /* Queue length         */
#define ifr_newname     ifr_ifru.ifru_newname   /* New name             */
#define ifr_settings    ifr_ifru.ifru_settings  /* Device/proto settings*/

#include <sys/stat.h>

struct stat {
    dev_t     st_dev;         /* ID of device containing file */
    ino_t     st_ino;         /* inode number */
    mode_t    st_mode;        /* protection */
    nlink_t   st_nlink;       /* number of hard links */
    uid_t     st_uid;         /* user ID of owner */
    gid_t     st_gid;         /* group ID of owner */
    dev_t     st_rdev;        /* device ID (if special file) */
    off_t     st_size;        /* total size, in bytes */
    blksize_t st_blksize;     /* blocksize for filesystem I/O */
    blkcnt_t  st_blocks;      /* number of 512B blocks allocated */

     /* Since Linux 2.6, the kernel supports nanosecond
      precision for the following timestamp fields.
     For the details before Linux 2.6, see NOTES. */

    struct timespec st_atim;  /* time of last access */
    struct timespec st_mtim;  /* time of last modification */
    struct timespec st_ctim;  /* time of last status change */

   #define st_atime st_atim.tv_sec      /* Backward compatibility */
   #define st_mtime st_mtim.tv_sec
   #define st_ctime st_ctim.tv_sec
};

#include <dirent.h>

struct dirent {
    ino_t          d_ino;       /* inode number */
    off_t          d_off;       /* not an offset; see NOTES */
    unsigned short d_reclen;    /* length of this record */
    unsigned char  d_type;      /* type of file; not supported
                                   by all filesystem types */
    char           d_name[256]; /* filename */
};

posted @ 2018-04-21 15:59  Jimmy_Nie  阅读(3578)  评论(0编辑  收藏  举报