【Socket】实现MiniHttpServer

简介: 【Socket】实现MiniHttpServer

HTTP服务器

图片来源于网络,侵删。

前言

实现一个http 服务器项目,服务器启动后监听80端口的tcp 连接,当用户通过任意一款浏览器(IE、火狐和腾讯浏览器等)访问我们的http服务器,http服务器会查找用户访问的html页面是否存在,如果存在则通过http 协议响应客户端的请求,把页面返回给浏览器,浏览器显示html页面;如果页面不存在,则通知浏览器此页面不存在(404 NOT FOUND)

什么是HTML

全称Hypertext Markup Language,也就是"超文本链接标示语言"。

HTML文本是由HTML命令组成的描述性文本。


HTTP协议

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web ) 服务器传输超文本到本地浏览器的传送协议

客户端发起请求

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。

image-20220504231634136

示例:

GET /demo.html HTTP/1.1
Host: 12.120.162.171
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2767.400
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie:cna=BT0+EoVi1FACAXH3Nv5I7h6k;isg=BIOD99I03BNYvZDfE2FJUOsMB0ftUBZcBFi4E7VgYOJZdKOWPcvRinAl6kSfVG8y

服务端响应

服务器响应客户端的HTTP请求也由四个部分组成:

状态行、消息报头、空行和响应正文。

image-20220504232409774

示例:

HTTP/1.0 200 OK
Server: ZYX Server
Content-Type: text/html
Connection: Close
Content-Length: 526

xxx

image-20220505095643085


Mini HTTP服务器

执行流程

接收http请求——>解析http请求——>响应http请求

main.c

#include "minihttp.h"
#include <pthread.h>

int main(void) {

    int sock;
    struct sockaddr_in server_addr;
    sock = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVER_PORT);
    bind(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
    listen(sock, 128);
    printf("Wait conecting......\n");
    int done = 1;

    while (done) {
        struct sockaddr_in client;
        int client_socket;
        int len;
        char client_ip[64];
        char buf[256];

        pthread_t id;//存储线程id
        int* pclient_sock = NULL;


        socklen_t client_addr_len;
        client_addr_len = sizeof(client);
        client_socket = accept(sock, (struct sockaddr*)&client, &client_addr_len);
        printf("client address:%s\tport:%d\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)), ntohs(client.sin_port));

        //处理Http请求,读取客户端发来的数据
        //do_http_request(client_socket);
        
        //启动线程,处理HTTP请求
        
        pclient_sock = (int*)malloc(sizeof(int));
        *pclient_sock = client_socket;
        pthread_create(&id,NULL,do_http_request,(void*)pclient_sock);
    }

    close(sock);

    return 0;
}

minihttp.h

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/stat.h>


#define SERVER_PORT 80 //端口

static int debug = 1;


//处理请求的每行数据
//返回值:-1读取出错,=0表示读到空行,>0表示成功读取。
int get_line(int sock, char* buf, int size);

//读取客户端发来的http请求
void* do_http_request(void* client_sock);
                                                            
//根据请求返回内容
void do_http_response(int client_sock, const char* path);

//响应404
void not_found(int client_sock);                

//返回请求头
int headers(int client_sock,FILE* resource); 

//发送html文件中的内容
void cat(int client_sock,FILE* resource);    

//服务器内部错误500
void iner_error(int client_sock);     

//响应未定义的请求
void unimplemented(int client_sock);    

minihttp.c

#include "minihttp.h"

int get_line(int sock, char* buf, int size) {

    int count = 0;
    char ch = '\0';
    int len = 0;

    while ((count < size - 1) && ch != '\n') {
        len = read(sock,&ch,1);

        if (len == 1) {
            if (ch == '\r') {
                continue;
            } else if (ch == '\n') {
                
                break;//读取完毕
            }

            //这里处理一般的字符
            buf[count] = ch;
            count++;
        } else if(len == -1){//读取出错
            perror("read failed!");
            count = -1;
            break;
        } else {//返回0——客户端关闭socket链接
            fprintf(stderr,"clinet close\n");
            count = -1;
            break;
        }
    }
    if (count >= 0) {
        buf[count] = '\0';//添加字符串结束符
    }

    return count;
}


void* do_http_request(void* pclient_sock){
    
    int len = 0;
    char buf[256];
    char method[64];
    char url[256];
    char path[256];

    int client_sock = *(int*)pclient_sock;

    struct  stat st;
    //读取请求行 
    
    len = get_line(client_sock, buf, sizeof(buf));

    if (len > 0) {//读到了请求行数(第一行)
        int i = 0;
        int j = 0;

        while (!isspace(buf[j]) && i <sizeof(method)-1) {
            method[i] = buf[j];
            i++;
            j++;
        }

        method[i] = '\0';
        printf("request method:%s\n",method);


        if (strncasecmp(method, "GET", i) == 0) {//只处理get请求
            if (debug) {
                printf("method = GET\n");
            }

            //获取url
            while (isspace(buf[j++])) {//跳过空格
                i = 0;
            }

            while (!isspace(buf[j]) && i < sizeof(url) - 1) {
                url[i] = buf[j];
                i++;
                j++;
            }

            url[i] = '\0';

            if (debug) {
                printf("url:%s\n", url);
            }
            //继续读取http头部
            do 
            {
                len = get_line(client_sock, buf, sizeof(buf));
                if(debug){
                    printf("read:%s\n",buf);
                }
            } while (len>0);

            //定位服务器本地的html文件
            

            //处理url中的?,过滤掉?后面的内容
            {
                char* pos = strchr(url, '?');
                if (pos) {
                    *pos = '\0';
                    printf("real url:%s\n",url);
                }
            }

            sprintf(path,"./html_docs/%s",url);

            if (debug) {
                printf("path:%s\n", path);
            } 

            //get请求服务端回复
            //判断文件是否存在,如果存在就相应200 OK,同时发送相应的html文件
            //如果不存在就相应404 not found 
            
            if (stat(path, &st) == -1) {//文件不存在或者出错
                fprintf(stderr,"stat %s failed,reason:%s\n",path,strerror(errno));
                not_found(client_sock);
            } else {//文件存在

                if (S_ISDIR(st.st_mode)) {//如果是目录,添加默认网页
                    strcat(path,"/index.html");
                }

    
                do_http_response(client_sock,path);
            }


        } else {//非get请求,读取http头部,并相应客户端501
            fprintf(stderr,"warning! other rquest [%s]\n",method);
            do 
            {
                len = get_line(client_sock, buf, sizeof(buf));
            } while (len>0);
            
            unimplemented(client_sock);
        }
    } else {//请求格式有问题,出错处理。

        not_found(client_sock);
    }

    close(client_sock);

    if (pclient_sock) {
        free(pclient_sock);
    }

    return NULL;
}


void do_http_response(int client_sock, const char* path) {

    int ret = 0;

    FILE *resource = NULL;

    resource = fopen(path,"r");

    if (resource == NULL) {
        not_found(client_sock);
        return;
    }


    //发送http头部
    ret = headers(client_sock, resource);

    if (!ret) {
        //成功发送头部后
        //发送http body
        cat(client_sock, resource);
    }

    fclose(resource);
}

void not_found(int client_sock){
    const char* reply = "HTTP/1.0 404 NOT FOUND\r\n\
        Content - Type: text / html\r\n\
        \r\n\
        <HTML>\r\n\
        <HEAD>\r\n\
        <TITLE>NOT FOUND</TITLE>\r\n\
        </HEAD>\r\n\
        <BODY>\r\n\
        <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
        </BODY>\r\n\
        </HTML>\r\n";
    
    int len = write(client_sock,reply,strlen(reply));
    
    if (len <= 0) {
        fprintf(stderr,"send reply failed,reason:%s\n",strerror(errno));
    }

    //if (debug) {
    //    fprintf(stdout,reply);
    //}
}

int headers(int client_sock, FILE* resource) {

    struct stat st;

    int fileid = 0;

    char tmp[128];

    char buf[1024] = { 0 };

    strcpy(buf, "HTTP/1.0 200 OK\r\n");
    strcat(buf, "Server: XUANXUAN Server\r\n");
    strcat(buf, "Content-Type:text/html\r\n");
    strcat(buf, "Connection: Close\r\n");

    fileid = fileno(resource);                        //拿到文件fd——文件描述符

    if (fstat(fileid, &st) == -1) {
        iner_error(client_sock);
        return -1;//失败
    }

    sprintf(tmp, "Content-Length:%ld\r\n\r\n", st.st_size);
    strcat(buf, tmp);

    if (debug) {
        fprintf(stdout, "header:%s\n", buf);
    }

    if (send(client_sock, buf, strlen(buf), 0) < 0) {//如果发送失败
        fprintf(stderr, "send failed.data:%s,reason:%s\n", buf, strerror(errno));
        return -1;
    }

    return 0;
}

void cat(int client_sock, FILE* resource) {

    char buf[1024];
    fgets(buf,sizeof(buf),resource);

    //没有到达文件尾部就一直读
    while (!feof(resource)) {
        int len = write(client_sock, buf, strlen(buf));

        if (len < 0) {//发送body的过程中出现问题
            fprintf(stderr,"send body error. reason:%s\n",strerror(errno));
            break;
        }

        if (debug) {
            fprintf(stdout,"%s",buf);
        }

        fgets(buf, sizeof(buf), resource);
    }

}

void iner_error(int client_sock) {
    const char* reply = "HTTP/1.0 500 Internal Sever Error\r\n\
        Content - Type: text / html\r\n\
        \r\n\
        <HTML>\
        <HEAD>\
        <TITLE>inner_error</TITLE>\
        </HEAD>\
        <BODY>\
        <P>Error prohibited CGI execution.\
        </BODY>\
        </HTML>";

    int len = write(client_sock, reply, strlen(reply));

    if (len <= 0) {
        fprintf(stderr, "send reply failed,reason:%s\n", strerror(errno));
    }

    /*if (debug) {
        fprintf(stdout, reply);
    }*/
}

void unimplemented(int client_sock) {
    const char* reply = "HTTP/1.0 404 NOT FOUND\r\n\
        Content - Type: text / html\r\n\
        \r\n\
        <HTML>\r\n\
        <HEAD>\r\n\
        <TITLE>NO Implemented</TITLE>\r\n\
        </HEAD>\r\n\
        <BODY>\r\n\
        <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
        </BODY>\r\n\
        </HTML>\r\n";

    int len = write(client_sock, reply, strlen(reply));

    if (len <= 0) {
        fprintf(stderr, "send reply failed,reason:%s\n", strerror(errno));
    }
}

并发

什么是并发?

通俗的并发通常是指同时能并行的处理多个任务。

程序同时拥有两个或多个线程,如果程序在单核处理器上运行,多个线程将交替的换入或者换出内存,这些线程是同时"存在"的。同一时间点上,只能有一个线程执行。

每个线程都处于执行中的某个状态,如果运行在多核处理器上,此时,程序中的每个线程都将分配到一个处理器核上,因此可以同时运行。

高并发

高并发是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够 同时并行处理很多请求

注意:

编译时记得加声明 -pthread

gcc minihttp.c main.c -pthread -o minihttp

连不上记得使用root用户执行。

相关文章
|
传感器 网络协议 物联网
Linux MQTT通信:实现轻量级物联网传输协议
MQTT(Message Queuing Telemetry Transport)是一种轻量级的物联网传输协议,专门设计用于低带宽、不稳定网络环境下的传感器和物联网设备通信。本文将深入探讨Linux环境下如何实现MQTT通信,介绍MQTT协议的基本原理、常用MQTT库以及如何在Linux系统中编写MQTT客户端和服务器端程序。
2744 0
|
存储 SQL 机器学习/深度学习
用户画像标签体系——从零开始搭建实时用户画像(三)
用户画像标签体系——从零开始搭建实时用户画像(三)
4291 0
用户画像标签体系——从零开始搭建实时用户画像(三)
|
2月前
|
人工智能 测试技术 Shell
Claude Code 用了两周后,我发现它最强的不是写代码
Claude Code 不是普通AI编程助手,它深度融入终端工作流:读项目、跑测试、分析报错、看diff、管提交、记规则(CLAUDE.md)。它不只补代码,而是参与完整工程链路——从需求理解到文档沉淀。真正价值在于“工程化协作”,而非局部辅助。
|
11月前
|
敏捷开发 机器学习/深度学习 人工智能
需求重复、碎片化、无追溯?2025年智能化需求管理工具的关键应用
本文系统梳理需求管理工具从文档管理到AI驱动的四代技术演进,解析现代智能系统的核心功能架构与技术实现,并基于六维评估模型对比主流工具优劣。结合四阶段实施方法论与典型问题解决方案,为企业选型与落地提供全面指导。
502 0
|
资源调度 前端开发 Android开发
如何在 React Native 中进行代码签名验证?
如何在 React Native 中进行代码签名验证?
|
人工智能 自然语言处理 供应链
科技云报到:RPA怎么了?2025年或将强的可怕!
科技云报到:RPA怎么了?2025年或将强的可怕!
485 1
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
592 17
|
SQL 人工智能 自然语言处理
NL2SQL进阶系列(2):DAIL-SQL、DB-GPT开源应用实践详解Text2SQL
NL2SQL进阶系列(2):DAIL-SQL、DB-GPT开源应用实践详解Text2SQL
NL2SQL进阶系列(2):DAIL-SQL、DB-GPT开源应用实践详解Text2SQL
|
存储 缓存 Apache
Apache Paimon 在蚂蚁的应用
本文整理自 Apache Paimon Committer 闵文俊老师在5月16日 Streaming Lakehouse Meetup · Online 上的分享。Apache Paimon 是一种实时数据湖格式,设计用于流批一体处理,支持实时更新和OLAP查询。它采用LSM Tree结构,提供多种Changelog Producer和Merge Engine,支持高效的数据合并。Paimon适用于流读、批读及时间旅行查询,与多种查询引擎兼容。在蚂蚁集团的应用中,Paimon降低了资源开销,提升了查询性能,简化了研发流程,特别是在去重、核对场景和离线查询加速方面表现突出。
1760 7
Apache Paimon 在蚂蚁的应用
|
人工智能 编译器 Go
Go 哪里没有做好?Rob Pike 深刻反思了
Go 哪里没有做好?Rob Pike 深刻反思了