手撸简单http服务器

最近要用c语言写一个简单的http服务器,实现html的deliver和404处理
由于是第一次写服务器,花了点精力,终于实现了,在这里分享一下

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netinet/in.h>
#include<pthread.h>
#include<sys/socket.h>
#include<netdb.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<ctype.h>

#define SERVER_STRING "Server: httpT/0.1.0\r\n"  
#define MAX_WAIT 5//服务器最大等待数

int startup(u_short *port);//生成套接字socket(),套接字和客户端地址绑定,分配端口,开始监听请求listen()
void accept_request(int client_sock);//处理客户端请求
int get_line(int sock, char *buf, int size);//从套接字socket中读取一行放在buf缓冲区中、返回buf中存储的数据长度(不包括\0)
void serve_file(int client_sock,const char* filename);//把filename这个文件加上http头后发给客户端
void header(int client_sock, const char *filename);//给客户端发送http头
void cat(int client_sock,FILE* fp);//给客户端发送请求的文件的内容
void not_found(int client_sock);//请求的文件404 not found 返回给客户端404错误信息
void error_die(const char* msg);//错误,终止进程

int main()
{
    int server_sock = -1;//服务器端socket
    int client_sock = -1;//客户端socket
    u_short port = 44444;//端口号
    struct sockaddr_in client_name;//客户端地址数据结构
    int client_name_len = sizeof(client_name);
    pthread_t thread;//线程变量

    server_sock = startup(&port);
    printf("http server running on port:%d\n",port);

    while(1)//无限循环等待请求
    {
        client_sock = accept(server_sock,(struct sockaddr*)&client_name,&client_name_len);//接受客户端请求
        if(client_sock == -1)
            error_die("accept() error");
        if(pthread_create(&thread , NULL, accept_request, client_sock) != 0)
            error_die("pthread_create() error");
    }
    close(server_sock);//关闭服务器socket,结束程序

    return 0;
}

int startup(u_short *port)
{
    int server_sock = -1;//客户端socket
    struct sockaddr_in name;

    server_sock = socket(PF_INET, SOCK_STREAM, 0);//根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源
    if(server_sock == -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(server_sock,(struct sockaddr*)&name,sizeof(name)) < 0)
        error_die("bind() error");

    if(*port == 0)
    {
        int namelen = sizeof(name);  
        if(getsockname(server_sock,(struct sockaddr*)&name,&namelen) < 0)//如果端口是0,把随机分配给server_sock的端口取出来赋值给port
            error_die("getsockname() error");

        *port = ntohs(name.sin_port);
    }
    //开始监听
    if(listen(server_sock,MAX_WAIT) < 0)
        error_die("listen() error");

    return server_sock;
}

int get_line(int client_sock,char* buf,int size)
{
    //用recv()逐个字符从client_sock中接受一行http请求
    char c = '\0';
    int i = 0;
    int n;
    while((i < size - 1) && (c != '\n'))
    {
        n = recv(client_sock,&c,1,0);
        if(n > 0)
        {
            if(c == '\r')
            {
                n = recv(client_sock,&c,1,MSG_PEEK);
                if((n > 0) && (c == '\n'))
                {
                    recv(client_sock,&c,1,0);
                }
                else
                {
                    c = '\n';
                }
            }
            buf[i] = c;
            i++;
        }
        else
        {
            c = '\n';
        }
    }
    buf[i] = '\0';
    return i;
}


void accept_request(int client_sock)
{
    char buf[256];
    char file_path[256];
    char url[256];
    char method[50];
    int numchars = 0;
    struct stat st;//文件
    int i = 0;
    char* ptr = 0;//用来遍历缓存buf

    get_line(client_sock,buf,256);
puts(buf);
    ptr = buf;
    while(!isspace(*ptr))//取出请求method
    {

        method[i] = *ptr;
        i++;
        ptr++;
    }
    method[i] = '\0';  
    i=0;
    ptr++;
    while(!isspace(*ptr))//取出请求的文件路径
    {
        file_path[i] = *ptr;
        i++;
        ptr++;
    }
    file_path[i] = '\0';  
    sprintf(url,"htdocs%s",file_path);
    if(url[strlen(url) - 1] == '/')
    {
        //如果这是一个目录,默认访问目录下的index.html
        strcat(url,"index.html");
    }
     //如果不是以/结尾的依然可能是一个目录文件夹

    if(stat(url,&st) < 0)
    {
        //把所有 headers 的信息都丢弃,这一步是必须的,就是在这里走了许多弯路!  
        numchars = get_line(client_sock, buf, sizeof(buf)); 
        while ((numchars > 0) && strcmp("\n", buf))  // read & discard headers 
        {
            numchars = get_line(client_sock, buf, sizeof(buf)); 

        }


        not_found(client_sock);
    }
    else
    {
        if ((st.st_mode & S_IFMT) == S_IFDIR)  //S_IFMT为文件类型的位遮罩,进行按位与运算后的结果与S_IFDIR比较,判断是否为文件夹
            strcat(url, "/index.html");  

        //下面可以deliver html文件了
        serve_file(client_sock,url);

    }

    close(client_sock);//断开与客户端的连接,http的特性
}

void serve_file(int client_sock,const char* filename)
{
    FILE* fp = NULL;

    int numchars = 1;  
    char buf[1024];  
    //读取并丢弃 header   
    buf[0] = 'A'; buf[1] = '\0';  
    while ((numchars > 0) && strcmp("\n", buf))  // read & discard headers  
        numchars = get_line(client_sock, buf, sizeof(buf));   


    if((fp = fopen(filename,"r")) == NULL)
        not_found(client_sock);
    else
    {
        header(client_sock,filename);//给客户端发送http头
        cat(client_sock,fp);//发送文件内容

    }  
    fclose(fp);
}

void header(int client_sock, const char *filename)
{
    char buf[256];
    (void)filename; /* could use filename to determine file type */  

    sprintf(buf,"HTTP/1.1 200 OK\r\n");
    puts(buf);
    send(client_sock,buf,strlen(buf),0);
    sprintf(buf,SERVER_STRING);
    send(client_sock,buf,strlen(buf),0);
    sprintf(buf,"Content-Type: text/html;charset=utf-8\r\n");
    send(client_sock,buf,strlen(buf),0);
    sprintf(buf,"\r\n");
    send(client_sock,buf,strlen(buf),0);

    return;
}

void cat(int client_sock,FILE* fp)
{
    char buf[1024];
    fgets(buf,sizeof(buf),fp);
    while(!feof(fp))
    {
        send(client_sock,buf,strlen(buf),0);
        fgets(buf,sizeof(buf),fp);
    }
}


void not_found(int client_sock)
{
    char buf[256];
    strcpy(buf,"HTTP/1.1 404 NOT FOUND\r\n");
    send(client_sock,buf,strlen(buf),0);
    strcpy(buf,SERVER_STRING);
    send(client_sock,buf,strlen(buf),0);
    strcpy(buf,"Content-Type: text/html;charset=utf-8;\r\n");
    send(client_sock,buf,strlen(buf),0);
    strcpy(buf,"\r\n\r\n");
    send(client_sock,buf,strlen(buf),0);
    strcpy(buf,"<html><head><title>not found</title><head><body>\r\n");
    send(client_sock,buf,strlen(buf),0);
    strcpy(buf,"<h1 style=\"text-align:center;\">ERROR: 404 NOT FOUND</h1></body></html>\r\n");
    send(client_sock,buf,strlen(buf),0);

    return;
}

void error_die(const char* msg)
{
    perror(msg);
    exit(1);    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值