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
相关产品推荐
相关产品推荐

