You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用C语言编写支持音视频进度调整的服务器?问题排查与解决

问题分析与解决方案

是的,你的视频无法拖动进度条、总是从头播放的核心原因就是服务器没有处理HTTP范围请求(Range Request),也没有返回对应的响应头。下面详细拆解原因和解决步骤:

为什么会出现这个问题?

当你拖动<video>的currentTime时,浏览器不会重新请求整个视频文件,而是会发送带有Range: bytes=xxx-的请求,要求服务器返回视频的某一段内容。但你的服务器现在不管什么请求都返回200 OK和完整的文件内容:

你的服务器响应头:

HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Length: 105270886

而Apache服务器正确处理了Range请求,返回206 Partial Content状态码,并带上了Content-Range等关键头,告诉浏览器返回的是哪一段内容:

Apache的响应头:

HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 58392576-105270885/105270886
Content-Length: 46878310

浏览器拿到完整文件后,无法定位到指定的时间点,只能从头播放;而拿到指定片段后,才能正确加载到对应的位置。

具体解决步骤

你需要修改服务器代码,实现Range请求解析和206 Partial Content响应的支持,具体如下:

1. 添加Range请求解析函数

先写一个工具函数,从请求头中提取Range的起始和结束字节:

// 解析Range头,返回值:1表示有Range请求,0表示无,-1表示解析失败
int parse_range(const char *request_buf, int file_total_len, long *start_byte, long *end_byte) {
    const char *range_header = strstr(request_buf, "Range: bytes=");
    if (!range_header) {
        *start_byte = 0;
        *end_byte = file_total_len - 1;
        return 0;
    }

    range_header += strlen("Range: bytes=");
    char *dash_pos = strchr(range_header, '-');
    if (!dash_pos) return -1;

    // 提取起始字节
    *dash_pos = '\0';
    *start_byte = atol(range_header);
    if (*start_byte < 0) *start_byte = 0;
    if (*start_byte >= file_total_len) *start_byte = file_total_len - 1;

    // 提取结束字节
    range_header = dash_pos + 1;
    if (*range_header == '\0') {
        *end_byte = file_total_len - 1;
    } else {
        *end_byte = atol(range_header);
        if (*end_byte >= file_total_len) *end_byte = file_total_len - 1;
        if (*end_byte < *start_byte) *end_byte = *start_byte;
    }

    return 1;
}

2. 修改send_file函数,支持范围请求

更新文件发送逻辑,根据Range请求返回对应的片段内容:

// 注意:新增request_buf参数,用于传递浏览器的请求头
void send_file(int c_sock, const char *file_path, const char *file_ext, const char *request_buf) {
    int fd = open(file_path, O_RDONLY);
    if (fd == -1) {
        // 返回404错误
        char err_head[1024];
        sprintf(err_head, "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\n\r\nFile Not Found");
        send(c_sock, err_head, strlen(err_head), 0);
        close(fd);
        return;
    }

    int file_total_len = lseek(fd, 0, SEEK_END);
    long start_byte, end_byte;
    int has_range = parse_range(request_buf, file_total_len, &start_byte, &end_byte);
    int send_content_len = end_byte - start_byte + 1;

    // 构建响应头
    char response_head[2048];
    const char *content_type = file_ext;
    if (strcmp(file_ext, ".mp4") == 0) {
        content_type = "video/mp4";
    }

    if (has_range == 1) {
        // 发送206 Partial Content响应
        sprintf(response_head, "HTTP/1.1 206 Partial Content\r\n"
                              "Content-Type: %s\r\n"
                              "Accept-Ranges: bytes\r\n"
                              "Content-Length: %d\r\n"
                              "Content-Range: bytes %ld-%ld/%d\r\n"
                              "\r\n", 
                content_type, send_content_len, start_byte, end_byte, file_total_len);
    } else {
        // 发送200 OK完整响应
        sprintf(response_head, "HTTP/1.1 200 OK\r\n"
                              "Content-Type: %s\r\n"
                              "Accept-Ranges: bytes\r\n"
                              "Content-Length: %d\r\n"
                              "\r\n", 
                content_type, file_total_len);
    }
    send(c_sock, response_head, strlen(response_head), 0);

    // 定位到文件的起始位置
    lseek(fd, start_byte, SEEK_SET);
    char buf[4096];
    int sent_len = 0;
    while (sent_len < send_content_len) {
        memset(buf, 0, sizeof(buf));
        int read_size = read(fd, buf, sizeof(buf));
        if (read_size <= 0) break;

        // 确保不超过需要发送的内容长度
        if (sent_len + read_size > send_content_len) {
            read_size = send_content_len - sent_len;
        }

        send_helper(c_sock, buf, read_size);
        sent_len += read_size;
    }

    close(fd);
}

3. 更新send_helper函数,传入客户端套接字

把全局的c_sock改成参数传递,避免全局变量的问题:

void send_helper(int c_sock, char *content, int size) {
    while (size > 0) {
        int sent = send(c_sock, content, size, 0);
        if (sent <= 0) return;
        size -= sent;
        content += sent;
    }
}

4. 修改请求接收逻辑,传递请求头

在接收浏览器请求后,把请求缓冲区传递给send_file:

while (1) {
    int c_sock = accept(s_sock, NULL, NULL);
    if (c_sock != -1) {
        char request_buf[4096];
        int nread = recv(c_sock, request_buf, sizeof(request_buf), 0);
        if (nread > 0) {
            request_buf[nread] = '\0'; // 确保字符串终止
            // 这里需要你自己解析请求中的文件路径(比如从GET /source/1.mp4提取file_path)
            // 假设你已经解析出file_path和file_ext
            send_file(c_sock, file_path, file_ext, request_buf);
        }
        close(c_sock);
    }
}

关键注意点

  • 添加Accept-Ranges: bytes头:这会告诉浏览器你的服务器支持范围请求,浏览器才会在拖动进度条时发送Range请求。
  • 处理边界情况:比如Range的起始字节超过文件长度、结束字节小于起始字节等,需要做容错处理,避免崩溃。
  • 正确计算发送长度:发送的内容长度应该是Range指定的片段长度,而不是整个文件的长度。

内容的提问来源于stack exchange,提问作者嬲你屋里娘

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 00:17:59