一个简易的http服务器。

10 篇文章 0 订阅
5 篇文章 0 订阅

从代码逻辑我们可以很清晰的看出简单的http服务器锁遵循的框架,以及后台调用的过程
并在此,应用了,多进程,进程间通信的管道,以及网络通信socket的知识

/*************************************************************************
    > File Name: httpd.c
    > Author:Victor Qi 
    > Mail:Victor_QiGetFuture@163.com 
    > Created Time: Fri 25 Mar 2016 12:30:13 AM CST
 ************************************************************************/

#include<stdio.h>//熟知的标准库
#include<sys/socket.h>//socket.h
#include<sys/types.h>//蕴含着linux系统下你看到的各种非人类却人性化的变量,而这种变量的出现就是原始的变量重命名后得来的
#include<netinet/in.h>//里面蕴含着网络协议结构体,通常是网络编程模型第一步就所需要的初始化
#include<arpa/inet.h>
#include<ctype.h>//判断字符是否满足条件,例如是否为数字,大小写字母什么的
#include<pthread.h>//线程库头文件
#include<sys/wait.h>//针对进程,让进程陷入等待阻塞状态,并且返回进程pid
#include<stdlib.h>//标准库头文件
#include<sys/stat.h>//包含文件结构信息内容
#include<unistd.h>//标准posix文件,包含大量系统调用函数
#include<string.h>//标准字符串头文件
#include<strings.h>//string.h的扩展


#define  ISspace(x)  isspace((int)(x))
#define  SEVER_SIRING "sever:jdbhttpd/0.1.0\r\n"


//函数列表
void accept_request(int);//监听http请求,实现服务器处理请求流程
void bad_request(int);//返回个给客户端错误请求,http状态码400
void cat(int,FILE*);//读取服务器某个文件写到socket套接字描述符
void cannot_execute(int);//处理发生在执行cgi程序时出现的错误。
void error_die(const char *);//将错误信息写到perror并退出。
void execute_cgi(int ,const char * ,const char * , const char *);//运行cgi程序的处理,是主要函数
int  get_line(int ,char *,int );//读取套接字的遗憾数据,将会车换行等情况作为统一换行符结束
void headers(int ,const char *);//吧http相应的头部写到套接字中进行传输
void not_found(int);//主要处理服务端找不到请求文件的情况
void sever_file(int ,const char *);//调用cat把服务器文件返回个浏览器。
int  startup(u_short *);//初始化httpd服务,包括建立套接字,绑定端口,进行监听。
void unimplemented(int);//返回给浏览器表明收到的http请求的method不被支持


//对于轻量级服务器的调用顺序为  main ->  startup -> accept_request  -> execute_cgi.

//在这里开始实现main()函数
int main(int argc ,char ** argv)
{
    int sever_sock      =-1;
    u_short  port       =0;
    int client_sock     =-1;
    struct sockaddr_in  client_name;
    int client_name_len =sizeof(client_name);
    pthread_t           newpthread;//定义一个线程变量




    sever_sock = startup(&port);//开启httpd服务,并且监听对应端口
    print("httpd runing on port %d \n",port);



    for(;;)
    {
        //套接字接收到客户端连接请求
        client_sock=accept(sever_sock,(struct sockaddr *)&client_name,&client_name_len);//其中(struct sockaddr *) 为通用套接字类型
        if(client_sock==-1)
        {
            error_die("accept");
        }
        if(pthread_create(&newpthread,NULL,accept_request,client_sock)!=0)
            perror("pthread_create failed");
    }
    close(sever_sock);
    return 0;
}
//包裹函数。
void error_die(const char * error)
{
    perror(error);
    exit(1);
}

int startup(u_short *port)
{
    int     httpd=0;
    struct  sockaddr_in name;
    if(httpd== -1)
    {
        error_die("socket error");
    }
    memset(&name,0,sizeof(name));
    name.sin_family =AF_INET;
    name.sin_port=htons(*port);
    name.sin_addr.s_addr=htonl(INADDR_ANY);
    if(bind(httpd,(struct sockaddr*)&name,sizeof(name))<0)
        error_die("bind error");
    if(*port==0)
    {
        int namelen=sizeof(name);
        if(getsockname(httpd,(struct  sockaddr *)&name,&namelen))//动态随机分配端口号
        {
            error_die("getsockname error");
        }
        *port=ntons(name.sin_port);
    }
    if(listen(httpd,5)<0)
        error_die("listen");
    return httpd;
}
void accept_request(int client)
{
    char    buf[1024];
    int     numchar;
    char    method[255];
    char    url[255];
    cahr    path[512];
    size_t  i,j;
    int     cgi=0;
    char    *query_string=NULL;



    //判断请求文件头
    numchar=get_line(client,buf,sizeof(buf));
    i=0;j=0;
    while(!ISspace(buf[j])&&(i<sizeof(method)-1))
    {
        method[i]=buf[j];
        ++i;
        ++j;
    }
    method[i]='\0';
    //判断是什么请求
    if(strcasecmp(method,"GET")&&strcasecmp(method,"POST"))
    {
        unimplemented(client);
        return ;
    }
    if(strcasecmp(method,"POST")==0)
    {
        cgi=1;//开启状态位cgi
    }
    i=0while(ISspace(buf[j])&&(j<sizeof(buf)))
        j++;
    while(!ISspace(buf[j])&&(i<sizeof(url)-1)&&j<sizeof(buf))
    {
        //存下URL值
        url[i]=buf[j];
        i++;
        j++;
    }
    url[i]='\0';//字符串最末尾值

    //处理get请求
    if(strcasecmp(method,"GET")==0)
    {
        query_string=url;
        //之所以这么判断是因为get方法的特点是?后面是参数
        while((*query_string!='?')&&(*query_string!='\0'))
        {
            query_string++;
        }
        sprintf(path,"htdocs%s",url);//将html文件放在htdocs文件夹中
        if(path[strlen(path)-1]=='/')
            strcat(path,"index.html");
        if(stat(path,&st)==-1)
        {
            //丢弃headers头信息
            while((numchar>0)&&strcmp("\n",buf))
                numchar=get_line(client,buf,sizeof(buf));
            not_found(client);//对客户端进行回应
        }
        else
        {
            if((st.st_mode&S_IFMT)==S_IFDIR)
                strcat(path,"index.html");
            if((st.st_mode&S_IFUSR)||(st.s_mode&S_IXGRP)||(st.st_mode&S_IXOTH))
                cgi=1;
            //不是cgi,直接把服务器文件返回,否则执行cgi函数
            if(!cgi)
            {
                sever_file(client,path);
            }
            else
                execute_cgi(client,path,method,query_string);
        }
        close(client);//http特点: 无连接

    }
}


void bad_request(int client)
{
    char    buf[1024];
    //此处回应客户端错误的http请求
    sprintf(buf,"HTTP/1.1 400 BAD REQUEST\r\n");
    send(client,buf,sizeof(buf),0);
    sprintf(buf,"Content-type:text/html\r\n");
    send(client,buf,sizeof(buf),0);
    sprintf(buf,"\r\n");
    send(client,buf,sizeof(buf),0);
    sprintf(buf,"<p>your brower send a bad  request,");
    send(client,buf,sizeof(buf),0);
    sprintf(buf,"such as a POST without a Content-length.\t\n");
    send(client,buf,sizeof(buf),0);
}


void cat(int client,FILE*resourse)
{
    char    buf[1024];
    //读取文件信息并写入socket描述字中发送到浏览器端
    fgets(buf,sizeof(buf),strlen(buf),0);
    while(!feof(resourse))
    {   //返回文件数据并循环发送文件
        send(client,buf,sizeof(buf),0);
        fgets(buf,sizeof(buf),resourse);
    }
}

void  cannot_execute(int client)
{
    //返回服务器错误
    sprintf(buf,"HTTP/1.1 500 Internal Sever Error\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"Content-type:text/html\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"<p>Error prohibited CGI execution.\r\n");
    send(client,buf,strlen(buf),0);
}
void execute_cgi(int client,const char *path ,const char * method ,const char *query_string)
{
    char buf[1024];
    int cgi_output[2];//pipe 管道标识符,用于进程间通信
    int cgi_input[2];
    pid_t  pid;
    int status;
    int i;
    char c;
    int numchar  =1;
    int content_length=-1;



    buf[0]='A';
    buf[1]='\0'

    //读取文件头并丢弃 header
    if(strcasecmp(method,"GET")==0)//对于strcasecmp函数是比较两个字符串是否相等,不区分大小写
        while((numchar>0)&&strcmp("\n"buf))
            numchar=get_line(client,buf,sizeof(buf));//读文件头
    else
    {
        //获取信息长度
        numchar=get_line(client,buf,sizeof(buf));
        {
            buf[15]='\0';
            if(strcasecmp(buf,"content_length:")==0)
                content_length=atoi(&(buf[16]));获取信息长度
            numchar=get_line(client,buf,sizeof(buf));
        }
        if(content_length==-1)
        {
            bad_request(client);
            return;//请求错误
        }
    }
    sprintf(buf,"HTTP/1.1 200 OK\r\n");
    send(client,buf,strlen(buf),0);


    if(pipe(cgi_output)<0)
    {
        cannot_execute(client);
        return;
    }   
    if(pipe(cgi_input)<0)
    {
        cannot_execute(client);
        return;
    }
    if((pid=fork())<0)
    {   
        cannot_execute(client);
        return ;
    }
    if(pid==0)
    {
        char    meth_env[255];
        char    querry_env[255];
        char    length_env[255];


        dup2(cgi_input[0],0);//读端
        dup2(cgi_output[1],1);//写端
        close(cgi_input[1]);
        close(cgi_output[0]);

        //设置request_method的环境变量
        sprintf(meth_env,"REQUEST_METHOD=%s",method);
        putenv(meth_env);
        if(strcasecmp(method="GET")==0)
        {
            sprintf(querry_env,"QUERRY_STRING=%s",query_string);
            putenv(querry_env);
        }
        else
        {
            sprintf(length_env,"Content-type=%d",content_length);
            putenv(length_env);
        }
        execl(path,path,NULL);//用execl加载cgi程序
        exit(0);
    }
    else
    {
        close(cgi_output[1]);
        close(cgi_input[0]);
        //接受信息
        if(strcasecmp(method,"POST")==0)
            for(i=0;i<content_length;i++)
            {
                recv(client,&c,1,0);
                write(cgi_input[1],&c,1);
            }
        while(read(cgi_output[0],&c,1)>0)
            send(client,&c,1,0);
        close(cgi_output[0]);
        close(cgi_input[1]);//关闭管道
        waitpid(pid,&status,0);//等待子进程,防止僵死进程
    }
}
int get_line(int sock,cahr*buf,int size)
{
    int i=0;
    char c='\0';
    int n;
    //首先标准化buf数组
    while((i<size-1)&&(c!='\n'))
    {
        n=recv(sock,&c,1,0);
        if(n>0)
        {
            if(c=='\r')
            {
                n=recv(sock,&c,1,MSG_PEEK);
                if((n>0)&&(c=='\n'))
                    recv(sock,&c,1,0);
                else
                    c='\n';
            }
            buf[i]=c;
            i++;
        }       
        else
            c='\n';
    }
    buf[i]='\0';
    return i;
}

void headers(int client ,const char * filename)
{
    char buf[1024];
    (void)filename;//检测并显示文件类型
    strcpy(buf,"HTTP/1.1 200OK\r\n");
    send(client,buf,strlen(buf),0);
    strcpy(buf,SEVER_SIRING);
    send(client,buf,strlen(buf),0);
    sprintf(buf,"Content-type:text/html\r\n");
    send(client,buf,strlen(buf),0);
    strcpy(buf,"\r\n");
    send(client,buf,strlen(buf),0);
}


void not_found(int client)
{
    //返回http 404协议  
    char buf[1024];  
    //先发送http协议头  
    sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");  
    send(client, buf, strlen(buf), 0);  
    //再发送serverName  
    sprintf(buf, SERVER_STRING);  
    send(client, buf, strlen(buf), 0);  
    //再发送Content-Type  
    sprintf(buf, "Content-Type: text/html\r\n");  
    send(client, buf, strlen(buf), 0);  
    //发送换行符  
    sprintf(buf, "\r\n");  
    send(client, buf, strlen(buf), 0);  
    //发送html主体内容  
    sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "<BODY><P>The server could not fulfill\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "your request because the resource specified\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "is unavailable or nonexistent.\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "</BODY></HTML>\r\n");  
    send(client, buf, strlen(buf), 0);  
}
void sever_file(int client,const char * filename)
{
    //返回文件数据  
    FILE *resource = NULL;  
    int numchars = 1;  
    char buf[1024];  

    buf[0] = 'A';  
    buf[1] = '\0';  
    while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */  
        numchars = get_line(client, buf, sizeof (buf));  

    resource = fopen(filename, "r");  
    if (resource == NULL)  
        not_found(client);  
    else {  
        //先返回头部信息  
        headers(client, filename);  
        cat(client, resource);  
    }  
    fclose(resource);
}

void unimplemented(int client)
{
    char buf[1024];  

    sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, SERVER_STRING);  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "Content-Type: text/html\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "</TITLE></HEAD>\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "</BODY></HTML>\r\n");  
    send(client, buf, strlen(buf), 0);  

}
void PrintSocketAddress(const struct sockaddr *address, FILE *stream) {  
    // Test for address and stream  
    if (address == NULL || stream == NULL)  
        return;  

    void *numericAddress; // Pointer to binary address  
    // Buffer to contain result (IPv6 sufficient to hold IPv4)  
    char addrBuffer[INET6_ADDRSTRLEN];  
    in_port_t port; // Port to print  
    // Set pointer to address based on address family  
    switch (address->sa_family) {  
        case AF_INET:  
            numericAddress = &((struct sockaddr_in *) address)->sin_addr;  
            port = ntohs(((struct sockaddr_in *) address)->sin_port);  
            break;  
        case AF_INET6:  
            numericAddress = &((struct sockaddr_in6 *) address)->sin6_addr;  
            port = ntohs(((struct sockaddr_in6 *) address)->sin6_port);  
            break;  
        default:  
            fputs("[unknown type]", stream); // Unhandled type  
            return;  
    }  
    // Convert binary to printable address  

    if (inet_ntop(address->sa_family, numericAddress, addrBuffer,  
            sizeof (addrBuffer)) == NULL)  
        fputs("[invalid address]", stream); // Unable to convert  
    else {  
        fprintf(stream, "来自%s", addrBuffer);  
        if (port != 0) // Zero not valid in any socket addr  
            fprintf(stream, "-%u\n", port);  
    }  
}  
//主函数入口,编译时  
//gcc -W -Wall -lsocket -lpthread -o httpd httpd.c  在linux 可以去掉-lsocket  
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值