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

浏览器未向C Socket服务器发送完整POST数据问题排查

问题描述

我写了一个监听localhost:8080的简易C Socket服务器,会持续从客户端socket读取数据,直到消息末尾出现"quit"。

在Android端Chrome和Firefox浏览器测试时,发送如下无enctype="multipart/form-data"的POST请求:

<form
    action="http://localhost:8080"
    method="POST" >
    <input type="hidden" name="MAX_FILE_SIZE" value="100000"/>
    Choose a file to upload: <input name="uploadedfile" type="file"/><br/>
    <input type="submit" value="Upload File"/>
</form>

服务器仅能收到请求头,没有文件数据。Chrome发送的请求如下:

POST / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 52
Cache-Control: max-age=0
sec-ch-ua: "Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"
Origin: http://localhost:5000
DNT: 1
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:5000/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8,bn;q=0.7

但给form标签加上enctype="multipart/form-data"后,服务器能收到包含文件的完整多边界数据。

后续测试不带文件的POST请求:

<form
    action="http://localhost:8080"
    method="POST" >
    <input name="test" value="foobar"/>
    <input type="submit" value="Submit"/>
</form>

结果一致:不带multipart时仅收到请求头和urlencoded数据,带multipart时能收到完整数据。

补充:刷新浏览器关闭连接时,剩余的POST数据才会出现(POST后无CR LF结尾)。

我的疑问:

  1. 出现此现象的原因是什么?是否浏览器需要先收到响应才会继续发送后续数据?
  2. 目标是一次性接收完整POST数据,是否可行?
  3. 注意到multipart POST请求并未以两个CR LF结尾,仅以一个结尾,这是正常的吗?

服务器代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>

int main (int argc, char *argv[]) {
    int portno = argc == 2 ? atoi(argv[1]) : 8080;
    int srvfd = socket(AF_INET, SOCK_STREAM, 0);
    int option = 1;
    setsockopt(srvfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);
    if (bind(srvfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) < 0) {
        perror("server: error binding");
        exit(1);
    }
    listen(srvfd, 1000);
    printf("server: listening at port %d\n\n", portno);
    struct sockaddr_in cli_addr;
    socklen_t clilen = sizeof(cli_addr);
    int clifd = accept(srvfd, (struct sockaddr*) &cli_addr, &clilen);
    while (true) {
        char buffer[256];
        int n = read(clifd, buffer, 255);
        if (n == 0) {
            fprintf(stderr, "server: error: connection closed\n");
            exit(1);
        } else if (n < 0) {
            perror("server: error reading from socket");
            exit(1);
        }
        buffer[n] = 0;
        printf("%s", buffer);
        if (!strncmp(buffer, "quit", 4))
            exit(0);
    }
    return 0;
}

问题分析与解答

1. 现象原因:请求发送策略与服务器逻辑不匹配

当表单用默认的application/x-www-form-urlencoded编码时,浏览器会拆分请求头和请求体发送,但你的服务器没有遵循HTTP协议规范处理请求——既没解析Content-Length头来确定请求体大小,也没返回任何响应,浏览器会判定连接处于挂起状态,因此暂缓发送剩余的请求体数据。

而multipart/form-data是为文件上传设计的编码,浏览器会一次性发送完整请求(文件数据无法拆分),所以服务器能完整接收。你提到的"刷新关闭连接时剩余数据出现"也验证了这点:连接主动关闭时,浏览器会推送所有缓存的未发送数据。

2. 一次性接收完整POST数据完全可行

要实现这个目标,必须按照HTTP协议规范处理请求:

  • 第一步:读取并解析请求头,提取Content-Length字段的值,明确请求体总字节数
  • 第二步:根据Content-Length循环读取socket,直到接收完所有请求体数据

核心逻辑修改示例:

// 读取并解析请求头,提取Content-Length
char header_buffer[4096];
int header_len = read(clifd, header_buffer, sizeof(header_buffer)-1);
header_buffer[header_len] = '\0';
// 简化解析,实际需更严谨的字符串匹配
char *content_len_str = strstr(header_buffer, "Content-Length:");
int content_len = atoi(content_len_str + 15);

// 读取完整请求体
char *body_buffer = malloc(content_len + 1);
int total_read = 0;
while (total_read < content_len) {
    int n = read(clifd, body_buffer + total_read, content_len - total_read);
    if (n <= 0) {
        free(body_buffer);
        perror("read body error");
        exit(1);
    }
    total_read += n;
}
body_buffer[content_len] = '\0';
printf("完整请求体:%s\n", body_buffer);
free(body_buffer);

3. multipart请求的结尾是正常的

multipart/form-data请求的结尾格式为--边界值--\r\n,这里的单个\r\n符合HTTP规范。HTTP协议中,请求头与请求体之间用两个\r\n分隔,但multipart请求体的结束标记本身仅需一个\r\n收尾,属于正常现象。


内容的提问来源于stack exchange,提问作者AvirukBasak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 00:55:34