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

C语言新手求助:在UDP文件传输方案中实现CRC-16

嘿,作为刚入门C语言的新手,要在UDP文件传输方案里实现CRC-16校验其实没那么复杂,我帮你一步步拆解实现思路和代码修改方案~

先明确核心需求

你需要给UDP传输的每个数据包加上CRC-16校验,用来验证数据在传输过程中是否损坏。我们选用最常用的CRC-16-CCITT标准(多项式0x1021),这个兼容性好,实现也简单。

1. 先补全并整理你提供的基础代码

首先把你给出的代码片段补全并格式化:

#pragma comment(lib, "ws2_32.lib")
#include "stdafx.h"
#include <winsock2.h>
#include "ws2tcpip.h"
#include <stdio.h>
#include <stdint.h> // 要用到uint16_t等类型,必须加这个头

#define TARGET_IP "10.4.115.122"
//#define TARGET_IP "127.0.0.1"
#define BUFFERS_LEN 1024
#define SIZE_PACKET 10 // 最多可发送9999999999个数据包
#define CRC_LEN 2       // CRC-16占2字节
#define HEADER_LEN (SIZE_PACKET + 5 + CRC_LEN) // 原头部+2字节CRC字段
#define DATA_LEN (BUFFERS_LEN - HEADER_LEN)     // 实际可传输的数据长度

注意:必须引入<stdint.h>头文件,否则uint16_t这类标准整数类型会报错

2. 实现CRC-16核心计算函数

写一个通用的CRC-16计算函数,这是整个校验的核心:

// 计算CRC-16-CCITT校验值,输入数据指针和长度,返回16位校验值
uint16_t calculate_crc16(const uint8_t *data, size_t length) {
    uint16_t crc = 0xFFFF; // CRC初始值
    const uint16_t polynomial = 0x1021; // CRC-16-CCITT标准多项式

    for (size_t i = 0; i < length; i++) {
        crc ^= (uint16_t)data[i] << 8; // 把当前字节放到CRC的高8位
        for (int j = 0; j < 8; j++) {
            // 逐位处理,判断是否需要异或多项式
            if (crc & 0x8000) {
                crc = (crc << 1) ^ polynomial;
            } else {
                crc <<= 1;
            }
        }
    }
    return crc;
}
3. 发送端:给数据包附加CRC校验值

在发送UDP数据包前,先计算除CRC字段外所有数据的校验值,然后把校验值放到数据包的指定位置(这里我们放到头部的末尾):

// 假设你已经初始化好了Winsock和UDP套接字sock
SOCKADDR_IN target_addr;
target_addr.sin_family = AF_INET;
target_addr.sin_port = htons(8888); // 替换成你的目标端口
inet_pton(AF_INET, TARGET_IP, &target_addr.sin_addr);

char send_buffer[BUFFERS_LEN];
int packet_num = 1; // 当前发送的数据包序号
char control_data[5] = "CTRL"; // 示例控制数据,替换成你的实际控制位

// 1. 填充数据包头部:包序号 + 控制位
sprintf(send_buffer, "%010d", packet_num); // 把序号转成10位字符串
memcpy(send_buffer + SIZE_PACKET, control_data, 5); // 填充5字节控制数据

// 2. 填充要传输的实际数据(比如从文件读取的数据)
char file_data[DATA_LEN];
// 这里省略从文件读取数据的逻辑,假设已经把数据读到file_data里
memcpy(send_buffer + HEADER_LEN - CRC_LEN, file_data, DATA_LEN);

// 3. 计算CRC:计算头部(不含CRC字段)+ 数据的总长度的校验值
size_t calc_length = (HEADER_LEN - CRC_LEN) + DATA_LEN;
uint16_t crc_value = calculate_crc16((uint8_t*)send_buffer, calc_length);

// 4. 把CRC值转成网络字节序(大端),避免不同主机端序问题
crc_value = htons(crc_value);
// 把CRC值放到头部的最后2字节位置
memcpy(send_buffer + HEADER_LEN - CRC_LEN + DATA_LEN, &crc_value, CRC_LEN);

// 5. 发送数据包
sendto(sock, send_buffer, BUFFERS_LEN, 0, (SOCKADDR*)&target_addr, sizeof(target_addr));
4. 接收端:验证CRC校验值

接收数据包后,先提取CRC值,再计算数据部分的校验值,对比两者是否一致:

// 假设已经初始化好接收端的UDP套接字sock
char recv_buffer[BUFFERS_LEN];
SOCKADDR_IN sender_addr;
int addr_len = sizeof(sender_addr);

int recv_len = recvfrom(sock, recv_buffer, BUFFERS_LEN, 0, (SOCKADDR*)&sender_addr, &addr_len);
if (recv_len <= 0) {
    printf("接收数据包失败\n");
    return;
}

// 1. 提取接收到的CRC值,转为主机字节序
uint16_t received_crc;
memcpy(&received_crc, recv_buffer + recv_len - CRC_LEN, CRC_LEN);
received_crc = ntohs(received_crc);

// 2. 计算接收数据(除CRC字段外)的校验值
size_t calc_length = recv_len - CRC_LEN;
uint16_t calculated_crc = calculate_crc16((uint8_t*)recv_buffer, calc_length);

// 3. 对比校验结果
if (calculated_crc == received_crc) {
    printf("数据包校验通过!序号:%s\n", recv_buffer);
    // 校验通过,处理数据(比如写入文件)
    char* data_start = recv_buffer + HEADER_LEN - CRC_LEN;
    // 这里省略写入文件的逻辑
} else {
    printf("数据包校验失败,丢弃!\n");
    // 可以在这里实现重发请求逻辑(如果你的UDP方案支持)
}
关键注意事项
  • 字节序问题:一定要用htons()和ntohs()转换CRC值的字节序,因为UDP传输的是网络字节序(大端),不同主机的端序可能不同,不转换会导致校验失败。
  • 校验范围:计算CRC时,绝对不能把CRC字段本身包含进去,否则校验永远会通过,失去意义。
  • 错误处理:UDP本身是不可靠的,校验失败时应该触发重发逻辑(如果你的作业要求实现可靠传输的话)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:46:08