浏览器未向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结尾)。
我的疑问:
- 出现此现象的原因是什么?是否浏览器需要先收到响应才会继续发送后续数据?
- 目标是一次性接收完整POST数据,是否可行?
- 注意到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

