c++ 通过网络接收文件在接收时会丢失一些数据包

fdx2calv  于 2023-06-25  发布在  其他
关注(0)|答案(1)|浏览(176)

我用C++编写了一个服务器和客户端程序。我在这个项目中使用了winsock2.h。我使程序能够通过网络发送和接收文件。服务器程序和客户端程序是两个独立的项目,因为在同一个项目中制作程序是不可能检查它们的。
当我在一个设备上同时运行客户端和服务器,并尝试将文本从客户端发送到服务器,反之亦然,它成功了,甚至将文件从服务器发送到客户端,反之亦然。
当我在一个设备上运行服务器,在另一个设备上运行客户端时,发送文本成功(可能是因为它只是一个数据包)。然而,发送文件往往会有一些错误。我的数据包大小设置为1024字节,每当发送单个数据包时,它就可以工作。但是当我尝试发送多对数据包时,它在最后接收到多对数据包时失败。我尝试了多种大小的文件,它在最后几个数据包失败了多次。我以为问题出在我的基于堆栈的代码上,所以我试着把它改为堆,它...还是不行。我已经有一段时间没弄明白这个问题了。我试着检查可能有类似问题的地方,但我无法找到解决方案。
这些是我的代码:

    • 服务器程序接收功能**
bool Server::ReceiveFile()
{
    if (!is_still_connected(clientSocket)) { PRINT("Client has disconnected"); return false; }

    if (!is_ConnectedWithAClient)return false;

    //Receive File name to use 
    if (!ReceiveText()) {
        PRINT_ERR("Failed to receive file name!!");
        return false;
    }
    std::cout << "Received file name: " << text << std::endl;
    std::string filename = text;

    std::string filepath = File_Path + filename;

    // Receive the file size
    if (!ReceiveText())
    {
        PRINT_ERR("Failed to receive file size!!");
    }
    PRINT_LOG("Received File size: " + text + " bytes.");

    int fileSize = std::stoi(text);

    if (!CreateFilepath(filepath))return false;

    std::ofstream file(filepath, std::ios::binary);
    if (!file) {
        PRINT_ERR("Failed to create file: " + filename);
        return false;
    }
    PRINT_LOG("File created successfully at path: " + filepath);

    //char buffer[BUFFER_SIZE];
    char* buffer = new char[BUFFER_SIZE];
    int totalBytesReceived = 0;
    int emptycounter = 0;
    while (totalBytesReceived < fileSize)
    {
        bytesRecv = recv(clientSocket, buffer, BUFFER_SIZE - 1, 0);
        if (bytesRecv == SOCKET_ERROR) {
            PRINT_ERR("Failed to receive file data");
            return false;
        }

        totalBytesReceived += bytesRecv;

        if (bytesRecv < 1)  emptycounter++;
        else file.write(buffer, bytesRecv);

        if (emptycounter > 5)
        {
            if (totalBytesReceived < 1)PRINT_LOG("The file is empty!!");
            break;
        }

        PRINT_LOG("Bytes received for file: " + filename + " : " + std::to_string(bytesRecv));
        // Send acknowledgement message
        if (!SendText("ACK")) {
            PRINT_ERR("Failed to send acknowledgement message");
            delete[]buffer;
            return false;
        }

        // Wait for acknowledgement response from client
        if (!ReceiveText()) {
            PRINT_ERR("Failed to receive acknowledgement response");
            delete[]buffer;
            return false;
        }

        // Check if acknowledgement response is valid
        if (text != "ACK") {
            PRINT_ERR("Invalid acknowledgement response");
            delete[]buffer;
            return false;
        }
        Sleep(Sleep_timer);
        PRINT_LOG("Total Bytes received : " + filename + " : " + std::to_string(totalBytesReceived));
    }
    file.close();
    return true;
}
    • 客户端程序发送功能**
bool Client::SendFile(std::string filepath)
{
    if (!is_ConnectedWiththeServer)return false;

    // Open the file
    std::ifstream file(filepath, std::ios::binary);
    if (!file) {
        PRINT_ERR("Failed to open file: " + filepath);
        return false;
    }

    // Extract the file name from the file path
    size_t fileNameStart = filepath.find_last_of("\\/") + 1;
    std::string fileName = filepath.substr(fileNameStart);

    // Get file size
    file.seekg(0, std::ios::end);
    int fileSize = static_cast<int>(file.tellg());
    file.seekg(0, std::ios::beg);

    // Send file name 
    PRINT_LOG("Sending file: " + fileName);
    if (!SendText(fileName))
    {
        file.close();
        return false;
    }
    PRINT_LOG("Sent file name: " + fileName);

    // Send file size 
    PRINT_LOG("Sending file size: " + std::to_string(fileSize) + " bytes");

    std::string f_size = std::to_string(fileSize);
    if (!SendText(f_size))
    {
        file.close();
        return false;
    }
    PRINT_LOG("Sent file size: " + std::to_string(fileSize) + " bytes");

    // Send file contents
    //char buffer[BUFFER_SIZE];
    char* buffer=new char[BUFFER_SIZE];
    int totalBytesSent = 0;

    while (file)
    {
        file.read(buffer, BUFFER_SIZE-1);
        int bytesRead = static_cast<int>(file.gcount());
        bytesSent = send(clientSocket, buffer, bytesRead, 0);
        if (bytesSent == SOCKET_ERROR) {
            PRINT_ERR("Failed to send text");
            file.close();
            return false;
        }
        totalBytesSent += bytesSent;
        PRINT_LOG("Sent " + std::to_string(bytesSent) + " bytes from file");

        // Wait for acknowledgement message from server
        if (!ReceiveText()) {
            PRINT_ERR("Failed to receive acknowledgement message");
            delete[]buffer;
            return false;
        }

        // Send acknowledgement response to server
        if (!SendText("ACK")) {
            PRINT_ERR("Failed to send acknowledgement response");
            delete[]buffer;
            return false;
        }
        Sleep(Sleep_timer);
    }

    file.close();
    PRINT_LOG("Sent file data: " + std::to_string(totalBytesSent) + " bytes");

    return true;
}
    • 编辑**如果有必要,这些是发送和接收文本:
bool Server::ReceiveText()
{
    if (!is_still_connected(clientSocket)) { PRINT("Client has disconnected"); return false; }

    text = "";
    if (!CheckSocInit()) return false;
    char buffer[BUFFER_SIZE];
    bytesRecv = recv(clientSocket, buffer, BUFFER_SIZE - 1, 0);
    if (bytesRecv < 1) {
        PRINT_ERR("Failed to receive text");
        return false;
    }
    buffer[bytesRecv] = '\0';
    text += buffer;

    return true;
}
bool Server::SendText(std::string text)
{
    if (!is_still_connected(clientSocket)) { PRINT("Client has disconnected"); return false; }

    if (!is_ConnectedWithAClient)return false;
    bytesSent = send(clientSocket, text.c_str(), int(text.size()) + 1, 0);
    if (bytesSent == SOCKET_ERROR) {
        PRINT_ERR("Failed to send text");
        return false;
    }
    return true;
}

在客户端和服务器端几乎相同。只有一些标志检查,因为两者之间的差异
顺便说一句,有一些宏,我一直在使用,以简化我的理解的代码。下面是我在这些代码中使用的宏:

#define BUFFER_SIZE 10240

#define PRINT_ERR(x) std::cerr<<"ERROR: "<<x<<std::endl;
#define PRINT_LOG(x) std::clog<<"LOG: "<<x<<std::endl;
#define PRINT(x) std::cout<<x<<std::endl;

如果可能的话,请指出我在代码中所做的错误,这些错误可能会影响或可能不会影响这个问题。先谢谢你了!!

pqwbnv8z

pqwbnv8z1#

TCP处理的是一个 stream 的数据,所以你通过一次调用write发送的内容可能会也可能不会通过一次调用read读取。当您处理localhost时,它们通常会对应-但当数据通过“真实的”网络时,它们通常不会。
例如,假设我正在尝试发送一个名为text.txt的文件,其中包含“This is some text”。你现在发送东西的方式,这将到达接收端,看起来像这样:

text.txtThis is some text

没有任何东西可以告诉接收者文件的名称在哪里结束,文件的内容在哪里开始。现在,您依赖于您在一次调用write时发送的内容,该内容将通过一次调用read来读取。但这并不总是会发生的。例如,尽管上面的数据很短,但您完全有可能在第一次调用`read时收到 * 所有 * 数据(文件名和文件体)。
为了解决这个问题,你必须编写代码,使它不依赖于两个对应的。你通常通过在你发送的数据中写一些东西来明确地告诉接收者一件事在哪里结束,另一件事在哪里开始。
一种可能性是,当你发送文件名时,你可以用文件名的长度作为前缀,这样在接收端,你会读取长度字段,然后用它来读取文件名--而不阅读文件的任何内容,就好像它是文件名的一部分一样。

bool sendFile(std::string_view fileName, socket dest) {
    uint32_t len = htonl(fileName.length());

    write(dest, (char *)&len, sizeof(len)); // write file name length
    write(dest, filename.c_str(), len);     // write file name itself

    // repeat essentially the same thing for the body of the file.
    // write the length of the file, followed by the content of the file
}

然后在接收端,你基本上反映了这一点:

uint32_t len;

read(src, (char *)&len, sizeof(len)); // read filename length
len = ntohl(len);
read(src, buffer, len);                // read filename itself

// then read a length for the body, then the body itself

注意:要支持超过4GB的文件,您可能需要将主体长度的字段扩展为8字节而不是4字节。
但这过于简单化了。

  • 在读端,通常希望分配一个合理的、固定大小的缓冲区以供读取,而不是试图一次读取整个文件。
  • 一次读或写可能无法发送/接收你要求的全部数量,所以你通常需要写一个小循环:
while (bytes_to_send > 0) {
    auto bytes_sent = write(dest, buffer, len);
    if (bytes_sent > 0) 
        bytes_to_send -= bytes_sent;
    else {
       // writing failed--sort out what happened
    }
}

[……阅读起来也是一样]
根据具体情况,您可能还需要考虑在每个部分(文件名和文件体)的开头添加一个小ID。(例如,“FN”表示文件名,“BD”表示正文)。这使得将来扩展格式变得更加容易,如果您决定传输其他内容(例如,文件权限)。

相关问题