开篇:本文解决什么问题?

  • 拆解HTTP协议的核心特性与传输流程
  • 区分HTTP长连接与短连接的适用场景
  • 实现一个能处理静态资源的HTTP服务器

一、HTTP协议基础

1 什么是HTTP?

        HTTP(超文本传输协议)是基于TCP/IP的应用层协议,主要用于客户端与服务器之间的静态资源(如HTML、图片)传输,遵循请求-响应的交互模式。

2 HTTP的核心特性

        无状态:每次请求相互独立,服务器不存储客户端的会话信息

        明文传输:数据在网络中以明文形式传输,安全性较低

        基于TCP:依赖TCP的可靠传输特性,通信前需完成三次握手建立连接

3 无状态的弥补方案

为解决无状态导致的用户身份识别问题,方案如下:

        Cookie:服务器通过响应头向客户端下发小型数据,客户端后续请求自动携带

        Session:服务器为用户创建专属会话存储状态,通过Cookie传递 sessionID 匹配用户

        Token:客户端登录后获取加密令牌,后续请求携带令牌完成身份认证,无需服务器存储会话

4 HTTP与HTTPS的对比

对比维度 HTTP HTTPS
安全性 低,明文传输 高,基于SSL/TLS加密 
默认端口 80 443
地址标识  http://   https://  
适用场景 普通网页浏览 支付、登录等敏感数据传输 

二、传输过程

1. 建立TCP连接

客户端与服务器通过TCP三次握手建立可靠连接。

2. 发送HTTP请求

客户端发送符合协议格式的请求数据,包含请求行、请求头、请求体三部分。

        请求行:如  GET /index.html HTTP/1.1 ,包含请求方法、资源路径、协议版本

        请求头:携带附加信息(如浏览器类型、Cookie)

        请求体:仅POST请求存在,用于传输表单、JSON等数据

3. 服务器处理并响应

服务器解析请求,返回响应数据,包含响应行、响应头、响应体三部分。

        响应行:如  HTTP/1.1 200 OK ,包含协议版本、状态码、状态描述

        响应头:携带数据长度、数据类型等信息

        响应体:核心资源内容(如HTML代码、图片二进制数据)

4. 关闭TCP连接

短连接模式下,一次请求响应后通过四次挥手断开连接。

三、长连接与短连接

对比维度 短连接 长连接 
生命周期 一次请求-响应后关闭TCP连接 连接建立后可承载多次请求,空闲超时后关闭 
协议标识 HTTP/1.0 HTTP/1.1,通过 keep-alive 头字段标识 
资源消耗 高,频繁建立/断开连接 低,复用连接减少开销 
适用场景 简单页面 复杂页面(多图片、CSS、JS资源加载) 

四、辅助协议

        DNS:将域名转换为IP地址,解决“记域名比记IP更方便”的问题

        ARP:将IP地址转换为MAC地址,实现局域网内设备寻址

        DHCP:自动为设备分配IP地址、网关、DNS等网络配置

五、静态资源解析

1 核心概念

        index.html:网站默认首页,用户访问域名时若未指定文件,服务器自动返回该文件

        HTML:超文本标记语言,通过标签构建网页结构,核心标签包含 <html> 、 <head> 、  <body> 

2 lseek函数(文件指针操作)

在处理静态文件时, lseek 用于移动文件读写指针,实现文件随机访问,函数原型:

off_t lseek(int fd, off_t offset, int whence);

        fd:文件描述符

        offset:偏移字节数

        whence:基准位置( SEEK_SET -文件开头、 SEEK_CUR -当前位置、 SEEK_END -文件末尾)

六、实现HTTP服务器

1 功能说明

本代码基于TCP套接字和HTTP/1.0协议,实现一个简易HTTP服务器,核心功能如下:

        1. 监听本地 127.0.0.1:80 端口,接收客户端HTTP请求

        2. 解析GET请求中的资源路径,读取对应本地静态文件

        3. 若文件存在,返回 200 OK 状态码和文件内容

        4. 若文件不存在,返回 404 Not Found 状态码和预设的404错误页面

        5. 单线程阻塞处理,同一时间仅能响应一个客户端请求

2 代码实现

(1)头文件与宏定义

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

#define DATALENGTH 1024 // 数据缓冲区大小
#define PATHLENGTH 128 // 文件路径最大长度

(2)发送HTTP响应头

// 功能:构造并发送HTTP响应头
// 参数:c 客户端通信套接字,size 响应体大小,flag 文件存在标识(1存在/0不存在)
void SendHeadData(int c, int size, int flag) {
    char buff[DATALENGTH] = "HTTP/1.0";
    // 根据文件状态设置响应状态码
    if (flag) {
        strcat(buff, "200 OK\r\n");
    } else {
        strcat(buff, "404 Not Found\r\n");
    }
    // 填充响应头字段
    strcat(buff, "Server: MyWeb/1.0\r\n");
    strcat(buff, "Content-Length: ");
    sprintf(buff + strlen(buff), "%d", size);
    strcat(buff, "\r\n");
    strcat(buff, "Content-Type: text/html;charset=utf-8\r\n");
    strcat(buff, "\r\n"); // 响应头与响应体的分隔符(必须有)
    // 发送响应头
    send(c, buff, strlen(buff), 0);
}

(3)发送文件内容

 

// 功能:读取文件内容并发送给客户端(作为响应体)
// 参数:c 客户端通信套接字,fd 打开的文件描述符
void SendFileData(int c, int fd) {
    while (1) {
        char buff[DATALENGTH] = {0};
        // 分块读取文件内容
        ssize_t n = read(fd, buff, DATALENGTH - 1);
        // 读取完毕或失败则退出循环
        if (n <= 0) {
            break;
        }
        // 发送读取到的文件内容
        send(c, buff, n, 0);
    }
}

(4)处理客户端请求

// 功能:接收并解析客户端HTTP请求,处理静态文件读取与响应
// 参数:c 客户端通信套接字
void DealClientData(int c) {
    char requestBuff[DATALENGTH] = {0};
    // 接收客户端请求数据
    int n = recv(c, requestBuff, DATALENGTH - 1, 0);
    if (n <= 0) {
        return;
    }
    // 解析HTTP请求行,提取资源路径
    // 示例请求行:GET /index.html HTTP/1.1 → 切割后获取 /index.html
    char *file = strtok(requestBuff, " ");
    file = strtok(NULL, " ");
    int flag = 1; // 文件存在标识,默认存在
    char path[PATHLENGTH] = "./"; // 本地文件根路径
    strcat(path, file); // 拼接完整文件路径
    // 以只读方式打开请求的文件
    int fd = open(path, O_RDONLY);
    // 文件不存在则打开404错误页面
    if (fd == -1) {
        fd = open("./404.html", O_RDONLY);
        flag = 0;
    }
    // 获取文件状态(包含文件大小)
    struct stat st;
    fstat(fd, &st);
    // 发送HTTP响应头
    SendHeadData(c, st.st_size, flag);
    // 发送文件内容
    SendFileData(c, fd);
    // 关闭文件描述符
    close(fd);

}

 

(5)初始化服务器套接字

// 功能:创建、绑定、监听TCP套接字,完成服务器初始化
// 返回值:成功返回监听套接字,失败返回-1
int InitSocket() {
    // 1. 创建TCP套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        return -1;
    }
    // 2. 配置服务器地址结构体
    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET; // IPv4协议族
    saddr.sin_port = htons(80); // HTTP默认端口(转换为网络字节序)
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本地回环地址
    // 3. 绑定套接字与地址端口
    int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (res == -1) {
        return -1;
    }
    // 4. 开启监听(5为半连接队列最大长度)
    res = listen(sockfd, 5);
    if (res == -1) {
        return -1;
    }
    return sockfd;
}

(6)主函数

// 功能:程序入口,启动服务器并循环接收客户端连接
int main() {
    // 初始化服务器套接字
    int sockfd = InitSocket();
    // 断言检查初始化是否成功,失败则终止程序
    assert(sockfd != -1);
    // 循环等待客户端连接(服务器常驻运行)
    while (1) {
        struct sockaddr_in caddr; // 客户端地址结构体
        socklen_t len = sizeof(caddr);
        // 接受客户端连接(阻塞等待)
        int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
        if (c < 0) {
            continue;
        }
        // 处理客户端请求
        DealClientData(c);
        // 关闭客户端通信套接字
        close(c);
    }
    exit(0);
}

3 运行说明

(1)环境准备

        在代码同级目录创建 index.html (首页内容)和 404.html (404错误页面)

        确保Linux环境下安装gcc编译器

(2)编译代码

 gcc http_server.c -o http_server

(3)运行服务器

 ./http_server

(4)测试访问

        浏览器访问 http://127.0.0.1 ,可查看 index.html 内容

        访问不存在的文件(如 http://127.0.0.1/test.html ),将显示 404.html 内容

 

 

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐