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

C++ UDP套接字传输HTML文件时字节数异常问题求助

UDP文件传输字节数不符问题排查与修复

问题概述

使用C++ UDP套接字实现客户端-服务器文件传输,传输1,814,247字节的s.html文件后出现两个异常:

  1. 客户端扣除8字节头部后的接收字节统计为1,814,239字节,比原文件少8字节;
  2. 生成的目标文件大小仅1,810,500字节,与原文件差距较大。

数据包结构体定义

struct ack_packet {
    uint16_t chsum;
    uint16_t len;
    uint16_t ackno;
};
struct packet {
    uint16_t chsum;
    uint16_t len;
    uint16_t seqno;
    uint16_t finished;
    char data[500];
};

问题根源分析

1. 接收字节统计少8字节的原因

服务端发送最后一块数据包时,pkt.len被错误设置为lastChunkSize(仅数据长度),但实际数据包总长度应为8字节头部 + 数据长度。客户端通过s += numBytes -8统计有效数据,而服务端发送的数据包长度仅为lastChunkSize,导致numBytes-8比实际少8字节。

2. 目标文件大小远小于原文件的原因

  • 最后一块数据包发送长度错误:服务端发送最后一块时,sendto仅发送lastChunkSize字节,未发送完整的数据包头部+数据,导致客户端收到的packet结构体字段值混乱,receivedPacket.len无效。
  • ACK接收逻辑错误:服务端接收最后一块的ACK时,仅尝试接收2字节,但ack_packet实际是6字节,会导致ACK接收不完整;同时代码块未加花括号,recvfrom语句无论发送是否成功都会执行,干扰流程。
  • 客户端写入依赖错误字段:客户端用receivedPacket.len-8作为写入长度,但服务端错误设置len字段,导致写入长度远小于实际数据长度。
  • 循环边界处理遗漏:当文件大小刚好是chunkSize整数倍时,最后一块的finished标记逻辑存在疏漏,可能无法触发客户端的循环终止条件。

修复后的代码

服务端代码

const size_t chunkSize = 500;
// 计算总数据包数:向上取整,处理整除与非整除情况
uint16_t totalPackets = (fileSize + chunkSize - 1) / chunkSize;

for (uint16_t seqno = 0; seqno < totalPackets; ++seqno) {
    packet pkt{};
    pkt.seqno = seqno;
    pkt.chsum = 88; // 建议后续替换为真实校验和逻辑
    bool isLastPacket = (seqno == totalPackets - 1);
    pkt.finished = isLastPacket ? 1 : 0;

    // 计算当前块的数据长度
    size_t currentChunkSize = isLastPacket ? (fileSize % chunkSize) : chunkSize;
    if (isLastPacket && currentChunkSize == 0) {
        currentChunkSize = chunkSize; // 处理文件大小刚好整除的情况
    }
    pkt.len = currentChunkSize + 8; // 总长度=头部8字节+数据长度

    memcpy(pkt.data, fileContent.data() + seqno * chunkSize, currentChunkSize);

    // 发送完整数据包
    ssize_t b = sendto(newSock, &pkt, pkt.len, 0, &clntAddr, clntAddrLen);
    if (b < 0) {
        perror("sendto() failed");
        seqno--; // 发送失败,重发当前包
        continue;
    }

    // 接收完整的ACK包(6字节)
    ack_packet receivedPacket{};
    socklen_t addrLen = clntAddrLen;
    ssize_t ackBytes = recvfrom(newSock, &receivedPacket, sizeof(receivedPacket), MSG_WAITALL, &clntAddr, &addrLen);
    if (ackBytes < 0 || receivedPacket.ackno != seqno) {
        seqno--; // ACK错误或接收失败,重发当前包
    }
}

客户端代码

int s = 0;
struct sockaddr temp{};
socklen_t sz = sizeof(temp);
uint16_t waiting_for_package_x = 0;

// 以二进制模式打开文件,避免文本模式的换行符转换
ofstream outputFile("received_s.html", ios::binary);
if (!outputFile.is_open()) {
    DieWithSystemMessage("Failed to open output file");
}

while(true){
    packet receivedPacket{};
    ssize_t numBytes = recvfrom(sock, &receivedPacket, sizeof(receivedPacket), MSG_WAITALL, &temp, &sz);
    if (numBytes < 0) {
        DieWithSystemMessage("recvfrom() failed");
    }

    // 验证数据包长度是否合法
    if (numBytes != receivedPacket.len) {
        cout << "Invalid packet length: expected " << receivedPacket.len << ", got " << numBytes << endl;
        exit(-1);
    }

    if(receivedPacket.seqno != waiting_for_package_x){
        cout << "Out of order packet: received " << receivedPacket.seqno << ", expected " << waiting_for_package_x << endl;
        exit(-1);
    }

    size_t dataLen = receivedPacket.len - 8;
    s += dataLen;
    outputFile.write(receivedPacket.data, dataLen);
    // 检查写入是否成功
    if (!outputFile) {
        DieWithSystemMessage("File write failed");
    }

    // 发送完整ACK
    ack_packet ackPacket{};
    ackPacket.chsum = 0; // 建议后续替换为真实校验和逻辑
    ackPacket.ackno = receivedPacket.seqno;
    ackPacket.len = sizeof(ackPacket);
    sendto(sock, &ackPacket, ackPacket.len, 0, &temp, sz);

    waiting_for_package_x++;
    if(receivedPacket.finished == 1){
        break;
    }
}

// 刷新缓存并关闭文件,确保所有数据写入磁盘
outputFile.flush();
outputFile.close();

cout << "Total received data bytes (excluding headers): " << s << endl;

额外注意事项

  1. 二进制模式打开文件:必须添加ios::binary,避免Windows下文本模式自动转换换行符导致文件大小变化。
  2. 实现真实校验和:当前硬编码的chsum无法检测数据包损坏,建议用CRC32或简单异或校验增强可靠性。
  3. 添加超时重传:UDP是不可靠协议,当前仅在ACK不匹配时重发,需添加超时机制避免因ACK丢失导致死等。
  4. 传输前同步文件大小:服务端可先发送包含文件大小的控制包,客户端最终可校验接收文件的完整性。

内容的提问来源于stack exchange,提问作者Abdullah Taman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 16:43:23