引言

在学习了TCP协议编程之后,我们已经能够实现客户端和服务器之间的基本通信。然而,真正的互联网应用大多使用的是更高级的协议——HTTP协议

HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最广泛的协议之一。当你在浏览器中输入网址并回车时,浏览器与服务器之间就是通过HTTP协议进行通信的。

第一部分:HTTP协议基础

一、HTTP协议的特点

特性 说明
应用层协议 位于OSI模型的应用层
基于TCP HTTP协议在传输层使用TCP协议(默认端口80)
请求-响应模型 客户端发起请求,服务器返回响应
无状态协议 每个请求之间相互独立(Cookie解决状态问题)

二、HTTP请求方法

方法 说明 安全性 典型场景
GET 请求获取资源 安全(只读) 浏览网页、下载文件
POST 提交数据 不安全(修改服务器状态) 表单提交、登录注册
PUT 上传资源 不安全 文件上传
DELETE 删除资源 不安全 删除数据
HEAD 只请求头部 安全 检查资源是否存在
OPTIONS 查询支持的方法 安全 CORS预检请求

重点掌握GET与POST的区别:

对比项 GET POST
作用 获取资源 提交数据
数据位置 URL中 请求体中
数据长度限制 有(浏览器限制)
幂等性
缓存 可缓存 不可缓存

三、HTTP请求报文结构

GET /index.html HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
 

请求报文结构:

四、HTTP响应报文结构

HTTP/1.1 200 OK
Server: my_http_server
Content-Type: text/html
Content-Length: 127

<html>
<head><title>Test</title></head>
<body>Hello World</body>
</html>

响应报文结构:

五、HTTP状态码

分类 含义 典型状态码
1xx 信息类 100 Continue(继续发送)
2xx 成功类 200 OK(请求成功)
3xx 重定向类 301(永久重定向)、302(临时重定向)、304(未修改)
4xx 客户端错误 400(错误请求)、401(未授权)、403(禁止)、404(未找到)
5xx 服务器错误 500(内部错误)、503(服务不可用)

第二部分:HTTP服务器的实现

一、核心功能概述

我们要实现的HTTP服务器具备以下功能:

  1. 监听80端口(HTTP默认端口)

  2. 接收浏览器的HTTP请求

  3. 解析请求报文,获取请求的文件名

  4. 在本地查找文件

  5. 如果文件存在,返回200 OK和文件内容

  6. 如果文件不存在,返回404 Not Found

二、基础TCP服务器框架

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

#define PORT 80
#define BUFFER_SIZE 1024
#define FILE_BUFFER_SIZE 512
#define WEB_ROOT "/home/user/mycode/http"  // 网站根目录

// 创建并初始化套接字
int init_socket() {
    // 1. 创建套接字
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        perror("socket error");
        return -1;
    }
    
    // 2. 设置端口复用(解决TIME_WAIT问题)
    int opt = 1;
    setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    // 3. 绑定地址和端口
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    if (bind(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind error");
        close(sock_fd);
        return -1;
    }
    
    // 4. 监听
    if (listen(sock_fd, 5) == -1) {
        perror("listen error");
        close(sock_fd);
        return -1;
    }
    
    printf("HTTP服务器启动成功,端口:%d\n", PORT);
    return sock_fd;
}

三、解析请求获取文件名

// 从HTTP请求报文中提取请求的文件名
char* get_filename(char* buffer) {
    // 按空格分割请求行
    char* method = strtok(buffer, " ");
    if (method == NULL) return NULL;
    
    // 获取请求的URL(如 /index.html 或 /)
    char* url = strtok(NULL, " ");
    if (url == NULL) return NULL;
    
    // 如果请求的是根路径(/),返回默认首页
    if (strcmp(url, "/") == 0) {
        return "index.html";
    }
    
    // 去掉开头的斜杠,返回相对路径
    return url + 1;
}

四、打开文件并获取文件大小

// 打开文件并获取文件大小
int open_file(const char* filename, off_t* file_size) {
    if (filename == NULL || file_size == NULL) return -1;
    
    // 拼接完整路径
    char fullpath[256];
    snprintf(fullpath, sizeof(fullpath), "%s/%s", WEB_ROOT, filename);
    
    // 打开文件
    int fd = open(fullpath, O_RDONLY);
    if (fd == -1) {
        printf("文件不存在: %s\n", fullpath);
        return -1;
    }
    
    // 获取文件大小
    struct stat st;
    if (stat(fullpath, &st) == -1) {
        close(fd);
        return -1;
    }
    
    *file_size = st.st_size;
    return fd;
}

五、发送HTTP响应头

// 发送200 OK响应头
void send_http_header(int client_fd, off_t file_size) {
    char header[512];
    
    // 状态行
    snprintf(header, sizeof(header),
        "HTTP/1.1 200 OK\r\n"
        "Server: my_http_server\r\n"
        "Content-Type: text/html\r\n"
        "Content-Length: %ld\r\n"
        "\r\n",
        file_size);
    
    send(client_fd, header, strlen(header), 0);
}

// 发送404 Not Found响应头
void send_404_header(int client_fd) {
    const char* header = 
        "HTTP/1.1 404 Not Found\r\n"
        "Server: my_http_server\r\n"
        "Content-Type: text/html\r\n"
        "Content-Length: 58\r\n"
        "\r\n"
        "<html><body><h1>404 Not Found</h1></body></html>";
    
    send(client_fd, header, strlen(header), 0);
}

六、发送文件内容

// 发送文件内容
void send_file_content(int client_fd, int file_fd) {
    char buffer[FILE_BUFFER_SIZE];
    ssize_t n;
    
    while ((n = read(file_fd, buffer, FILE_BUFFER_SIZE)) > 0) {
        send(client_fd, buffer, n, 0);
    }
}

七、主函数

int main() {
    int listen_fd = init_socket();
    if (listen_fd == -1) {
        exit(1);
    }
    
    while (1) {
        // 接受客户端连接
        int client_fd = accept(listen_fd, NULL, NULL);
        if (client_fd == -1) {
            perror("accept error");
            continue;
        }
        
        // 接收HTTP请求
        char buffer[BUFFER_SIZE];
        memset(buffer, 0, BUFFER_SIZE);
        int n = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
        
        if (n > 0) {
            printf("收到请求:\n%s\n", buffer);
            
            // 解析请求,获取文件名
            char* filename = get_filename(buffer);
            printf("请求文件: %s\n", filename ? filename : "(null)");
            
            // 打开文件并获取大小
            off_t file_size;
            int file_fd = open_file(filename, &file_size);
            
            if (file_fd == -1) {
                // 文件不存在,发送404
                send_404_header(client_fd);
            } else {
                // 文件存在,发送200 OK和文件内容
                send_http_header(client_fd, file_size);
                send_file_content(client_fd, file_fd);
                close(file_fd);
            }
        }
        
        // 关闭连接(短连接)
        close(client_fd);
    }
    
    close(listen_fd);
    return 0;
}

第三部分:编译与运行

一、编译注意事项

# 80端口需要管理员权限
sudo gcc http_server.c -o http_server

# 运行
sudo ./http_server

二、测试

  1. 启动服务器后,打开浏览器

  2. 访问 http://127.0.0.1/

  3. 服务器会返回 index.html 文件

  4. 访问 http://127.0.0.1/test.html(如果文件存在)


第四部分:HTML基础知识

一、HTML基本结构

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>页面标题</title>
</head>
<body>
    <h1>一级标题</h1>
    <p>这是一个段落。</p>
    <img src="image.jpg" alt="图片">
    <a href="next.html">下一页</a>
</body>
</html>

二、常用HTML标签

标签 作用
<h1> - <h6> 标题
<p> 段落
<a> 超链接
<img> 图片
<div> 区块容器
<span> 内联容器
<ul><li> 列表
<table> 表格

第五部分:HTTP长短连接

一、短连接

  • 每次请求都建立新的TCP连接

  • 请求结束后立即关闭连接

  • 适用于请求频率较低的场景

二、长连接

  • 多次请求复用同一个TCP连接

  • 通过Connection: keep-alive头部声明

  • 减少连接建立的开销,提高效率

// 长连接示例:循环处理同一个客户端的多个请求
while (1) {
    memset(buffer, 0, BUFFER_SIZE);
    int n = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
    if (n <= 0) break;  // 客户端关闭连接
    
    // 处理请求...
    // 不关闭连接,继续处理下一个请求
}

总结

一、HTTP核心知识点

知识点 说明
端口 默认80端口
请求方法 GET(获取)、POST(提交)
状态码 200(成功)、404(未找到)、500(服务器错误)
请求结构 请求行、请求头、空行、请求体
响应结构 状态行、响应头、空行、响应体
长短连接 Connection头部控制

二、实现HTTP服务器的关键步骤

  1. 创建TCP服务器,监听80端口

  2. 接收客户端连接

  3. 读取HTTP请求报文

  4. 解析请求行,获取请求的文件名

  5. 在本地查找文件

  6. 组装HTTP响应报文

  7. 发送响应头和文件内容

  8. 关闭连接(短连接)或保持连接(长连接)

本文从一个简单的HTTP服务器实现入手,深入讲解了HTTP协议的基本原理。理解HTTP协议不仅有助于网络编程,更是Web开发的基础。

学习建议:

  1. 动手实现HTTP服务器,观察浏览器请求的原始报文

  2. 理解GET和POST的区别

  3. 掌握常见状态码的含义

  4. 学习HTML基础知识,理解网页的构成

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐