C语言Web服务器无法向浏览器展示PNG文件的问题求助
问题现象
用C语言实现的Web服务器可正常展示txt和html文件,但PNG文件无法正常加载。访问http://localhost:10000/时,浏览器显示黑屏或中心小方块,同时触发ERR_CONTENT_LENGTH_MISMATCH错误。返回的HTTP响应头如下:
HTTP/1.1 200 OK
Content-Length: 177589
Connection: close
Content-Type: image/png
问题代码
#include <unistd.h> #include <stdio.h> #include <sys/socket.h> #include <stdlib.h> #include <netinet/in.h> #include <string.h> #include <stdlib.h> #define PORT 10000 int main() { FILE* file; char* file_name = "img.png"; int file_size; file = fopen(file_name, "r"); if (file == NULL) { printf("Error opening file!\n"); return 1; } fseek(file, 0, SEEK_END); file_size = ftell(file); rewind(file); // need http_header for proper HTTP response to client or there will be errors char http_header[2048] = {0}; sprintf(http_header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nConnection: close\r\nContent-Type: image/png\r\n\r\n", file_size); printf("%s", http_header); // buffer for client HTTP request char buffer[1024] = {0}; int server_fd; // defining socket's port and IP address struct sockaddr_in address; int addrlen = sizeof(address); // htons host-to-net short (16-bit) translation address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); // Creating socket file descriptor if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // options for the socket file descriptor int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt"); exit(EXIT_FAILURE); } // bind socket to the port and IP address if (bind(server_fd, (struct sockaddr *) &address, sizeof(address)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } // server starts listening and can have up to 3 max clients queued for backlog if (listen(server_fd, 3) < 0) { perror("listen"); exit(EXIT_FAILURE); } // client socket int new_socket; int valread; // while loop cause server will keep listening for requests while(1) { // address and addrlen are result arguments that will have the client's info // we redefined address and addrlen and we can probably use this later if ((new_socket = accept(server_fd, (struct sockaddr *) &address, (socklen_t * ) & addrlen)) < 0) { perror("accept"); exit(EXIT_FAILURE); } // we read in the client's HTTP request just for console output purposes valread = read(new_socket, buffer, 1024); printf("%s\n", buffer); // we send client socket the HTTP response send(new_socket, http_header, sizeof(http_header), 0); //send(new_socket, ht, sizeof(response), 0); char dbuffer[4096]; memset(dbuffer, 0, 4096); long bytes_read; if (file > 0) { while ((bytes_read = fread(dbuffer, 1, 4096, file)) > 0) { if (send(new_socket, dbuffer, sizeof(dbuffer), 0) < 0) { perror("Error\n"); return 1; } } } close(new_socket); } fclose(file); //printf("Hello message sent\n"); return 0; }
问题原因及修复方案
1. 二进制文件打开方式错误
PNG是二进制文件,用fopen(file_name, "r")文本模式打开会触发换行符等转义操作,破坏二进制数据,同时导致ftell获取的文件大小不准确。
修复:改用二进制模式打开:
file = fopen(file_name, "rb");
2. HTTP头发送长度错误
当前用sizeof(http_header)(固定2048字节)发送HTTP头,会把缓冲区里的空字节也发给客户端,导致响应体起始位置偏移,浏览器解析失败。
修复:用strlen(http_header)获取实际头长度发送:
send(new_socket, http_header, strlen(http_header), 0);
3. 文件内容发送长度错误
循环中用sizeof(dbuffer)(4096字节)发送文件内容,但fread返回的bytes_read才是实际读取的字节数(最后一次读取可能不足4096),这会发送多余空字节,造成Content-Length与实际发送字节数不匹配。
修复:发送时使用bytes_read作为长度:
if (send(new_socket, dbuffer, bytes_read, 0) < 0) { perror("Error\n"); return 1; }
4. 文件指针未重置
第一次请求后文件指针已指向末尾,后续请求无法读取内容。需在每次处理请求前重置指针,或重新打开文件(更安全,避免并发问题)。
修复(重新打开文件示例):
// 在处理每个客户端请求时重新打开文件 FILE* file = fopen(file_name, "rb"); if (!file) { char* not_found = "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n404 Not Found"; send(new_socket, not_found, strlen(not_found), 0); close(new_socket); continue; } // 重新计算文件大小 fseek(file, 0, SEEK_END); long file_size = ftell(file); rewind(file); // 重新生成HTTP头 char http_header[2048] = {0}; sprintf(http_header, "HTTP/1.1 200 OK\r\nContent-Length: %ld\r\nConnection: close\r\nContent-Type: image/png\r\n\r\n", file_size); // 发送头和文件内容... // 发送完成后关闭文件 fclose(file);
5. 文件大小类型溢出风险
int file_size无法容纳超过2GB的大文件,建议改用long类型:
long file_size;
同时sprintf格式符改为%ld:
sprintf(http_header, "HTTP/1.1 200 OK\r\nContent-Length: %ld\r\nConnection: close\r\nContent-Type: image/png\r\n\r\n", file_size);
内容的提问来源于stack exchange,提问作者Billy Rogers

