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所述内容,必须具备一下的基础知识:

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

转载于:https://www.cnblogs.com/Jimmy1988/p/8901841.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值