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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 00:05:03