C++基于Windows Socket传输图片损坏,文本文件正常的问题排查
Windows Socket传输PNG文件损坏问题排查与修复
问题现象
基于Windows Socket开发的C++通信模块,传输文本文件正常,但传输PNG图片时,服务器接收的文件损坏无法打开。
客户端代码问题分析
原客户端相关代码
// 读取二进制数据 int imgBinaryExtractor(){ int i = 0; std::ifstream file1; char ch; // 存储图片字符到img_binary const char* sfile = "original.png"; file1.open(sfile, std::ios::binary); while (file1.get(ch)) { img_binary[i] = ch; // 将PNG数据存入数组 file_size++; // 记录图片大小 i++; // 计数器 } file1.close(); std::cout << "file_size: " << file_size << std::endl; int a = 512; while(controle_file_size <= a){ data = data + img_binary[controle_file_size]; controle_file_size++; } a = controle_file_size + 512; return 0; } // 发送数据 int data_send(std::string data){ std::cout << "\nData send has been called"; sent_message = "\nSending Image"; send(client.socket, sent_message, strlen(sent_message), 0); sent_message = data.c_str(); send(client.socket, sent_message, strlen(sent_message), 0); sent_message = "\nThe image has been send"; send(client.socket, sent_message, strlen(sent_message), 0); } // 主函数 int main(){ std::thread worker1(conectar, "127.0.0.1", "8080"); // 连接线程 std::cout << "\nPress any button to send the image"; system("pause"); std::thread worker2(data_send, data); // 数据发送线程 while(controle_file_size < file_size){ imgBinaryExtractor(); std::cout << data; worker2.join(); // 等待线程结束 } worker1.join(); std::cout << "\nThe image has been sent" << std::endl; }
客户端核心问题
- 二进制数据存储与发送长度错误:
std::string以'\0'为结束标志,PNG二进制内容包含的'\0'会导致strlen(data.c_str())计算长度失真,发送时丢失大量字节;send应传入实际字节数data.size()而非字符串长度。 - 分段读取逻辑错误:
imgBinaryExtractor仅处理前512字节,后续分段逻辑未正确循环,无法完整读取整个文件。 - 线程使用错误:
worker2线程只能被join一次,循环中重复调用会触发未定义行为,且当前逻辑仅发送一次数据,无法完成分段传输。 - 全局变量滥用:
img_binary、file_size等全局变量在多线程环境下易引发数据竞争和逻辑混乱。
服务器端代码问题分析
原服务器端相关代码
// 接收数据循环 while (1) { if (new_client.socket != 0) { int iResult = recv(new_client.socket, tempmsg, DEFAULT_BUFLEN, 0); std::cout << "\nSize of buffer is: " << strlen(tempmsg); if (iResult != SOCKET_ERROR) { if (strcmp("", tempmsg)) { std::cout << tempmsg; binario = tempmsg; binaryStorer(binario); } } } } // 写入二进制文件 int binaryStorer(std::string binario){ int i = 0; std::ofstream file2; const char* tfile = "passed-image.png"; file2.open(tfile, std::ios::binary); file2 << binario; file2.close(); return 0; }
服务器端核心问题
- 接收长度判断错误:
strlen(tempmsg)会被二进制中的'\0'截断,应使用recv返回的iResult作为实际接收字节数。 - 二进制转字符串丢失数据:
binario = tempmsg转换时遇到'\0'停止,丢失部分二进制数据。 - 文件写入逻辑错误:每次调用
binaryStorer都重新打开文件并覆盖原有内容,仅保留最后一次接收的数据;应使用追加模式或保持文件打开状态。 - 未处理客户端断开:
recv返回0表示客户端断开,未做处理会导致无效循环。
修复方案
客户端修复代码示例
#include <iostream> #include <fstream> #include <string> #include <thread> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") struct Client { SOCKET socket; }; Client client; // 分段读取并发送文件 void send_file(const std::string& filename, SOCKET sock) { std::ifstream file(filename, std::ios::binary | std::ios::ate); if (!file.is_open()) { std::cerr << "Failed to open file" << std::endl; return; } std::streampos file_size = file.tellg(); file.seekg(0, std::ios::beg); const int BUFFER_SIZE = 512; char buffer[BUFFER_SIZE]; std::streamsize bytes_read; // 先发送文件大小 send(sock, reinterpret_cast<const char*>(&file_size), sizeof(file_size), 0); // 分段发送文件内容 while ((bytes_read = file.read(buffer, BUFFER_SIZE).gcount()) > 0) { send(sock, buffer, bytes_read, 0); } file.close(); std::cout << "File sent completely" << std::endl; } void conectar(const char* ip, const char* port) { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); client.socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(ip); server_addr.sin_port = htons(atoi(port)); connect(client.socket, reinterpret_cast<sockaddr*>(&server_addr), sizeof(server_addr)); } int main() { std::thread worker1(conectar, "127.0.0.1", "8080"); worker1.join(); // 等待连接建立完成 std::cout << "\nPress any button to send the image"; system("pause"); send_file("original.png", client.socket); closesocket(client.socket); WSACleanup(); std::cout << "\nThe image has been sent" << std::endl; return 0; }
服务器端修复代码示例
#include <iostream> #include <fstream> #include <string> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") struct Client { SOCKET socket; int id; }; Client new_client; const int DEFAULT_BUFLEN = 512; // 接收文件并保存 void receive_file(SOCKET sock) { std::streampos file_size; // 先接收文件大小 recv(sock, reinterpret_cast<char*>(&file_size), sizeof(file_size), 0); std::ofstream file("passed-image.png", std::ios::binary); if (!file.is_open()) { std::cerr << "Failed to open output file" << std::endl; return; } char buffer[DEFAULT_BUFLEN]; int bytes_received; std::streampos total_received = 0; while (total_received < file_size) { bytes_received = recv(sock, buffer, DEFAULT_BUFLEN, 0); if (bytes_received <= 0) { break; // 客户端断开或出错 } file.write(buffer, bytes_received); total_received += bytes_received; } file.close(); std::cout << "File received completely, size: " << total_received << " bytes" << std::endl; } int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(8080); bind(listen_sock, reinterpret_cast<sockaddr*>(&server_addr), sizeof(server_addr)); listen(listen_sock, SOMAXCONN); new_client.socket = accept(listen_sock, nullptr, nullptr); std::cout << "Client connected" << std::endl; receive_file(new_client.socket); closesocket(new_client.socket); closesocket(listen_sock); WSACleanup(); return 0; }
关键修复点说明
- 二进制数据处理:改用char数组缓冲区直接操作字节流,使用实际读取/接收的字节数(
gcount()、recv返回值)替代字符串长度函数。 - 文件传输流程优化:先发送文件大小,让服务器明确接收总字节数,循环分段传输直到文件处理完成。
- 线程与资源管理:确保连接建立完成后再发送数据,正确关闭Socket和清理WSA资源。
- 文件写入逻辑:服务器端保持文件打开状态直到接收完成,避免覆盖已写入内容。
内容的提问来源于stack exchange,提问作者iumonyty
相关产品推荐
相关产品推荐

