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 */
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM