欢迎光临
我们一直在努力

TinyWebServer源码解析——HTTP机制

项目地址
https://github.com/qinguoyi/TinyWebServer.git
尊重开源精神,保障作者权益,内容均为原创,如有雷同,纯属巧合

笔者能力有限,我认为这个部分还有更多的技术栈能够写出来,但是我的能力做不到了,如果以后有机会,我会再写一个修订版。敬请期待!

定义了一个http_conn类

实现了一个基于Linux epoll I/O 多路复用模型的高性能 HTTP 服务器核心模块


http_conn类
封装内容
  • void init()函数初始化连接
  • void close_conn()函数关闭连接
  • void process()主处理函数,由epoll触发后调用
  • bool read_once()函数一次性读取数据
  • bool write()函数发送响应数据
  • HTTP_CODE process_read()函数驱动状态机解析HTTP请求
  • bool process_write()函数生成HTTP响应
  • bool add_response()该函数格式化字符串并追加到写缓冲区
  • bool add_content()函数添加内容到响应体
  • bool add_status_line()函数添加状态行
  • bool add_headers()函数添加通用响应头
  • bool add_content_length()函数单独添加content_length头
  • bool add_linger()函数添加Connection: keep-alive 或 close
  • bool add_blank_line()函数表示响应头结束
  • LINE_STATUS parse_line()函数从m_read_buf中查找完整一行
  • HTTP_CODE parse_request_line()函数解析请求行
  • HTTP_CODE parse_headers()函数解析单个请求头字段
  • HTTP_CODE parse_content()函数处理POST请求体
  • HTTP_CODE do_request()函数决定返回什么内容
  • void initmysql_result()函数一次性从数据库加载所有用户到全局

  • 事件驱动(Event-Driven)
    组成:epoll+非阻塞I/O模型

    程序不主动轮询或阻塞等待 I/O,而是注册对某些“事件”的兴趣,当事件发生时,由系统通知程序进行处理

    核心组件:
    组件作用
    epoll Linux 高效 I/O 多路复用机制(替代 select/poll)
    非阻塞 socket 避免 read/write 阻塞线程
    事件循环(Event Loop) 主线程不断调用 epoll_wait() 等待事件
    回调/处理函数 事件触发时执行的逻辑(如 http_conn::process())
    具体体现:
    • 主线程使用epoll_wait()监听多个socket事件
    • 当某个客户端socket可读或者可写,epoll通知服务器
    • 非阻塞socket+ET/LT模式
    状态机(State Machine)

    HTTP 请求是分阶段、异步到达的(可能分多个 TCP 包),状态机能增量解析,避免等待完整数据,提升响应速度和内存效率

    状态任务
    解析请求行 找到第一行 "GET …"
    解析请求头 逐行读取 Host:, Content-Length: 等
    解析请求体 如果是 POST,读取 username=… 这部分内容
    将复杂的任务拆分为小步骤
    体现位置:HTTP请求解析过程
    具体体现:
    • 定义枚举状态CHECK_STATE

    enum CHECK_STATE
    {
    CHECK_STATE_REQUESTLINE = 0, //解析请求行
    CHECK_STATE_HEADER, //解析请求头
    CHECK_STATE_CONTENT //解析请求体
    };

    • process_read()状态转移

    http_conn::HTTP_CODE http_conn::process_read()
    {
    LINE_STATUS line_status = LINE_OK;
    HTTP_CODE ret = NO_REQUEST;
    char *text = 0;

    while ((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) || ((line_status = parse_line()) == LINE_OK))
    {
    text = get_line(); //获取当前行
    m_start_line = m_checked_idx;
    LOG_INFO("%s", text);
    switch (m_check_state)
    {
    case CHECK_STATE_REQUESTLINE:
    {
    ret = parse_request_line(text); //可能转移到HEADER
    if (ret == BAD_REQUEST)
    return BAD_REQUEST;
    break;
    }
    case CHECK_STATE_HEADER:
    {
    ret = parse_headers(text); //可能转移到CONTENT或完成
    if (ret == BAD_REQUEST)
    return BAD_REQUEST;
    else if (ret == GET_REQUEST)
    {
    return do_request();
    }
    break;
    }
    case CHECK_STATE_CONTENT:
    {
    ret = parse_content(text); //完成
    if (ret == GET_REQUEST)
    return do_request();
    line_status = LINE_OPEN;
    break;
    }
    default:
    return INTERNAL_ERROR;
    }
    }
    return NO_REQUEST;
    }

    零拷贝(Zero-Copy)
    组成:mmap()+writev()发送文件
    具体体现
    • 传统发送文件需要数据从磁盘 → 内核 → 用户 → 内核 → 网卡,4次上下文切换,4次数据拷贝
    • 零拷贝实现

    // 1. mmap 将文件映射到用户空间(实际是内核页缓存的映射)
    m_file_address = (char*)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, m_file_fd, 0);

    // 2. 构造 iovec 数组(响应头 + 文件内容)
    m_iv[0].iov_base = m_write_buf; // 响应头(小)
    m_iv[0].iov_len = m_write_idx;
    m_iv[1].iov_base = m_file_address; // 文件内容(大)
    m_iv[1].iov_len = m_file_stat.st_size;

    // 3. 一次系统调用发送全部
    ssize_t ret = writev(m_sockfd, m_iv, 2);

    • CUP不参与数据的搬运,在用户态与内核态之间无冗余拷贝
    • mmap本身不是零拷贝,需要配合writev/sendfile才能实现高效传输
    三者联系:

    MySQLStatic Filehttp_connepoll (事件驱动)ClientMySQLStatic Filehttp_connepoll (事件驱动)Client#mermaid-svg-5sp2YHcDfiSRyUPD{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5sp2YHcDfiSRyUPD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5sp2YHcDfiSRyUPD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5sp2YHcDfiSRyUPD .error-icon{fill:#552222;}#mermaid-svg-5sp2YHcDfiSRyUPD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5sp2YHcDfiSRyUPD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5sp2YHcDfiSRyUPD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5sp2YHcDfiSRyUPD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5sp2YHcDfiSRyUPD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5sp2YHcDfiSRyUPD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5sp2YHcDfiSRyUPD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5sp2YHcDfiSRyUPD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5sp2YHcDfiSRyUPD .marker.cross{stroke:#333333;}#mermaid-svg-5sp2YHcDfiSRyUPD svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5sp2YHcDfiSRyUPD p{margin:0;}#mermaid-svg-5sp2YHcDfiSRyUPD .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5sp2YHcDfiSRyUPD text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-5sp2YHcDfiSRyUPD .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5sp2YHcDfiSRyUPD .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-5sp2YHcDfiSRyUPD .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-5sp2YHcDfiSRyUPD .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-5sp2YHcDfiSRyUPD #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-5sp2YHcDfiSRyUPD .sequenceNumber{fill:white;}#mermaid-svg-5sp2YHcDfiSRyUPD #sequencenumber{fill:#333;}#mermaid-svg-5sp2YHcDfiSRyUPD #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-5sp2YHcDfiSRyUPD .messageText{fill:#333;stroke:none;}#mermaid-svg-5sp2YHcDfiSRyUPD .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5sp2YHcDfiSRyUPD .labelText,#mermaid-svg-5sp2YHcDfiSRyUPD .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-5sp2YHcDfiSRyUPD .loopText,#mermaid-svg-5sp2YHcDfiSRyUPD .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-5sp2YHcDfiSRyUPD .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5sp2YHcDfiSRyUPD .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-5sp2YHcDfiSRyUPD .noteText,#mermaid-svg-5sp2YHcDfiSRyUPD .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-5sp2YHcDfiSRyUPD .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5sp2YHcDfiSRyUPD .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5sp2YHcDfiSRyUPD .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5sp2YHcDfiSRyUPD .actorPopupMenu{position:absolute;}#mermaid-svg-5sp2YHcDfiSRyUPD .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-5sp2YHcDfiSRyUPD .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5sp2YHcDfiSRyUPD .actor-man circle,#mermaid-svg-5sp2YHcDfiSRyUPD line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-5sp2YHcDfiSRyUPD :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}alt[解析完成且是静态文件][CGI 请求]发送 HTTP 请求(分包)触发 EPOLLIN 事件状态机解析(逐行)mmap 映射触发 EPOLLOUT(若需)writev(mmap地址) → 零拷贝发送查询/插入(非零拷贝路径)write() 发送动态内容


    HTTP请求(HTTP Request)

    进入网页时,浏览器向服务器发送一个请求

    结构:

    GET /index.html HTTP/1.1 ← 请求行(方法 + 路径 + 协议版本)
    Host: example.com ← 请求头(Header)
    User-Agent: Chrome/120
    Connection: keep-alive

    ← 空行(分隔头和体)
    username=admin&password=123 ← 请求体(Body,仅 POST/PUT 有)

    • GET请求通常没有请求体,参数在URL中
    • POST请求由请求体
    响应头(Response Header)

    服务器在返回内容前,先发送一些元信息(metadata),告诉浏览器“接下来要发什么

    实例:

    HTTP/1.1 200 OK ← 状态行(协议 + 状态码 + 描述)
    Content-Type: text/html ← 响应头
    Content-Length: 1024
    Connection: keep-alive
    Set-Cookie: sessionid=abc123

    ← 空行(头结束)

    <html>… </html> ← 响应体

    常见头字段及作用:
    头字段作用
    Content-Type 告诉浏览器内容类型(text/html, image/png, application/json)
    Content-Length 响应体有多少字节
    Connection 是否保持连接(keep-alive 或 close)
    Location 用于重定向(302 状态码)
    响应体(Response Body)

    服务器真正返回给你的内容

    实例:

    <html>
    <head><title>Hello</title></head>
    <body><h1>Welcome!</h1></body>
    </html>

    mmap映射(Memory Mapping)

    一种将文件直接映射到内存的技术。你可以像访问数组一样访问文件内容,而无需调用 read()

    • 传统方式:

    char buffer[4096];
    int fd = open("a.jpg", O_RDONLY);
    while (read(fd, buffer, 4096) > 0)
    {
    send(socket, buffer, ...); // 数据:磁盘 → 内核 → 用户 → 内核 → 网卡(多次拷贝)
    }

    • mmap方式:

    int fd = open("a.jpg", O_RDONLY);
    struct stat sb; fstat(fd, &sb);
    char* data = (char*)mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); // 现在 data 就是文件内容!
    send(socket, data, sb.st_size, ...); // 内核直接从页缓存发数据,无需用户缓冲区

    优势:
    • 文件不经过用户空间缓冲区
    • CPU不参与数据搬运
    • 适合大文件传输
    协作关系:

    DBFile服务器(http_conn)浏览器DBFile服务器(http_conn)浏览器#mermaid-svg-dDmhw8mj2vgr99Ep{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dDmhw8mj2vgr99Ep .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dDmhw8mj2vgr99Ep .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dDmhw8mj2vgr99Ep .error-icon{fill:#552222;}#mermaid-svg-dDmhw8mj2vgr99Ep .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dDmhw8mj2vgr99Ep .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dDmhw8mj2vgr99Ep .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dDmhw8mj2vgr99Ep .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dDmhw8mj2vgr99Ep .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dDmhw8mj2vgr99Ep .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dDmhw8mj2vgr99Ep .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dDmhw8mj2vgr99Ep .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dDmhw8mj2vgr99Ep .marker.cross{stroke:#333333;}#mermaid-svg-dDmhw8mj2vgr99Ep svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dDmhw8mj2vgr99Ep p{margin:0;}#mermaid-svg-dDmhw8mj2vgr99Ep .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dDmhw8mj2vgr99Ep text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-dDmhw8mj2vgr99Ep .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-dDmhw8mj2vgr99Ep .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-dDmhw8mj2vgr99Ep .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-dDmhw8mj2vgr99Ep .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-dDmhw8mj2vgr99Ep #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-dDmhw8mj2vgr99Ep .sequenceNumber{fill:white;}#mermaid-svg-dDmhw8mj2vgr99Ep #sequencenumber{fill:#333;}#mermaid-svg-dDmhw8mj2vgr99Ep #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-dDmhw8mj2vgr99Ep .messageText{fill:#333;stroke:none;}#mermaid-svg-dDmhw8mj2vgr99Ep .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dDmhw8mj2vgr99Ep .labelText,#mermaid-svg-dDmhw8mj2vgr99Ep .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-dDmhw8mj2vgr99Ep .loopText,#mermaid-svg-dDmhw8mj2vgr99Ep .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-dDmhw8mj2vgr99Ep .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-dDmhw8mj2vgr99Ep .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-dDmhw8mj2vgr99Ep .noteText,#mermaid-svg-dDmhw8mj2vgr99Ep .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-dDmhw8mj2vgr99Ep .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dDmhw8mj2vgr99Ep .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dDmhw8mj2vgr99Ep .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dDmhw8mj2vgr99Ep .actorPopupMenu{position:absolute;}#mermaid-svg-dDmhw8mj2vgr99Ep .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-dDmhw8mj2vgr99Ep .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dDmhw8mj2vgr99Ep .actor-man circle,#mermaid-svg-dDmhw8mj2vgr99Ep line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-dDmhw8mj2vgr99Ep :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}alt[请求静态文件(如 /image.jpg)][请求登录(POST /login)]发送 HTTP 请求(请求行+头+体)用状态机逐步解析请求用 mmap 映射文件到内存发送响应头 + mmap 的内容(响应体)查询数据库发送 JSON 响应头 + {"status":"ok"}(响应体)

    术语一句话解释
    HTTP 请求 客户端问服务器:“我要什么?”
    响应头 服务器说:“我要给你的是 HTML,共 1024 字节,保持连接”
    响应体 服务器真正给你的内容(网页、图片、数据)
    状态机 服务器“边收边猜”客户端到底想干嘛(因为数据可能分批到)
    mmap 映射 服务器快速读大文件的“黑科技”,避免慢速拷贝

    函数返回为HTTP_CODE
    • 特殊含义:

    enum HTTP_CODE
    {
    NO_REQUEST, // 请求不完整,需要继续读
    GET_REQUEST, // GET 请求,可以处理
    BAD_REQUEST, // 400 错误:语法错误
    NO_RESOURCE, // 404 错误:文件不存在
    FORBIDDEN_REQUEST, // 403 错误:无权限
    FILE_REQUEST, // 静态文件请求,准备发送
    INTERNAL_ERROR, // 500 错误:服务器内部错误
    CLOSED_CONNECTION // 连接已关闭
    };

    • 语义相比于int或者bool,更加清晰
    • 统一错误处理机制
    • 支持更多中错误类型
    • 大量枚举的存在,增加代码容错率,以及更加清晰的状态返回参数
    赞(0)
    未经允许不得转载:171主机测评 » TinyWebServer源码解析——HTTP机制
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址