原文
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // 标准库函数,如 malloc, exit 等
#include <string.h> // 字符串操作,如 memset, memcpy 等
#include <unistd.h> // Unix标准函数,如 close 等
#include <sys/socket.h> // 套接字相关函数和常量
#include <arpa/inet.h> // IP地址转换函数(如 inet_ntoa, inet_addr 等)
#include <netinet/in.h> // 网络协议相关的结构和常量,如 sockaddr_in
#include <netinet/ip.h> // IP头部结构定义
#include <netinet/tcp.h> // TCP头部结构定义
#include <netinet/udp.h> // UDP头部结构定义
#include <netinet/ip_icmp.h> // ICMP头部结构定义
#include <errno.h> // 错误处理
// 校验和计算函数
unsigned short checksum(void *b, int len) {
unsigned short *buf = b;
unsigned int sum = 0;
for (; len > 1; len -= 2)
sum += *buf++;
if (len == 1)
sum += *(unsigned char *)buf;
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
return ~sum;
}
// UDP 校验和计算函数
unsigned short udp_checksum(struct iphdr *ip, struct udphdr *udp, char *payload, int payload_len) {
struct {
uint32_t src_addr;
uint32_t dest_addr;
uint8_t reserved;
uint8_t protocol;
uint16_t udp_length;
} pseudo_header;
pseudo_header.src_addr = ip->saddr;
pseudo_header.dest_addr = ip->daddr;
pseudo_header.reserved = 0;
pseudo_header.protocol = IPPROTO_UDP;
pseudo_header.udp_length = htons(sizeof(struct udphdr) + payload_len);
char buffer[4096];
memcpy(buffer, &pseudo_header, sizeof(pseudo_header));
memcpy(buffer + sizeof(pseudo_header), udp, sizeof(struct udphdr));
memcpy(buffer + sizeof(pseudo_header) + sizeof(struct udphdr), payload, payload_len);
return checksum(buffer, sizeof(pseudo_header) + sizeof(struct udphdr) + payload_len);
}
int main() {
char buffer[4096]; // 数据包缓冲区
memset(buffer, 0, sizeof(buffer));
// 创建原始套接字
int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项
int one = 1;
if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
perror("Error setting IP_HDRINCL");
exit(EXIT_FAILURE);
}
// 构造IP头
struct iphdr *ip = (struct iphdr *)buffer;
ip->ihl = 5; // Header length
ip->version = 4; // IPv4
ip->tos = 0; // Type of service
ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + 12); // IP头 + UDP头 + 数据长度
ip->id = htons(54321); // Packet ID
ip->frag_off = 0; // No fragmentation
ip->ttl = 64; // Time to live
ip->protocol = IPPROTO_UDP; // UDP协议
ip->saddr = inet_addr("192.168.122.85"); // 源IP地址
ip->daddr = inet_addr("8.8.8.8"); // 目标IP地址
ip->check = checksum((unsigned short *)ip, sizeof(struct iphdr)); // IP头校验和
// 构造UDP头
struct udphdr *udp = (struct udphdr *)(buffer + sizeof(struct iphdr));
udp->source = htons(12345); // 源端口
udp->dest = htons(53); // 目标端口(DNS端口示例)
udp->len = htons(sizeof(struct udphdr) + 12); // UDP头 + 数据长度
udp->check = 0; // 初始化校验和为0
// 数据负载(12字节示例)
char *payload = buffer + sizeof(struct iphdr) + sizeof(struct udphdr);
strcpy(payload, "Hello, UDP!");
// 计算UDP校验和
udp->check = udp_checksum(ip, udp, payload, strlen(payload));
// 目标地址
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_addr.s_addr = ip->daddr;
// 发送数据包
if (sendto(sockfd, buffer, ntohs(ip->tot_len), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
perror("Send failed");
} else {
printf("UDP Packet sent successfully!\n");
}
close(sockfd);
return 0;
}
头文件作用
<sys/socket.h>
提供套接字的基础函数和常量,例如socket
、sendto
、recvfrom
等。<arpa/inet.h>
提供IP地址的转换和处理函数,例如inet_ntoa
、inet_pton
。<netinet/in.h>
定义网络协议相关的数据结构,例如sockaddr_in
。<netinet/ip.h>
提供IP头部结构的定义,例如struct iphdr
。<netinet/tcp.h>
提供TCP头部结构的定义,例如struct tcphdr
。<netinet/udp.h>
提供UDP头部结构的定义,例如struct udphdr
。<netinet/ip_icmp.h>
提供ICMP头部结构的定义,例如struct icmphdr
。<errno.h>
处理错误码,例如通过errno
检查错误信息。<string.h>
用于字符串和内存操作,如memset
和memcpy
,在构造数据包时会频繁使用。<unistd.h>
提供POSIX标准函数,如close
。<stdlib.h>
提供内存管理(如malloc
)和退出函数(如exit
)。
代码解析
// 校验和计算函数
unsigned short checksum(void *b, int len) {
unsigned short *buf = b;
unsigned int sum = 0;
for (; len > 1; len -= 2)
sum += *buf++;
if (len == 1)
sum += *(unsigned char *)buf;
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
return ~sum;
}
函数功能
checksum
函数用于计算数据块的校验和,通常用于网络协议中(如IP、TCP、UDP),以检测数据传输中的错误。
参数说明
unsigned short checksum(void *b, int len)
b
: 指向需要计算校验和的缓冲区的指针。len
: 数据缓冲区的长度(以字节为单位)。
核心逻辑
1. 数据初始化
unsigned short *buf = b;
unsigned int sum = 0;
buf
: 将传入的void*
类型的指针b
转换为unsigned short*
类型,方便以2字节(16位)为单位处理数据。sum
: 用于累加数据的和,32位变量以避免溢出。
2. 数据块累加
for (; len > 1; len -= 2)
sum += *buf++;
以每次 2字节(16位) 为单位累加数据到
sum
。每次循环将指针
buf
移动到下一个2字节的数据块。len -= 2
表示处理了2字节数据后,减少数据剩余长度。
当数据块长度为偶数时,这部分会处理完所有数据。
3. 处理剩余的1字节
if (len == 1)
sum += *(unsigned char *)buf;
如果数据长度为奇数(
len == 1
),最后会剩余1字节的数据。通过类型转换
unsigned char*
取出最后的1字节,累加到sum
。
4. 高位与低位相加
sum = (sum >> 16) + (sum & 0xFFFF);
IP校验和的计算遵循 16位一组的加法,结果需要将溢出的高位加回低位:
(sum & 0xFFFF)
获取sum
的低16位。(sum >> 16)
获取sum
的高16位。两者相加将溢出的高位带回低位。
5. 再次处理溢出
sum += (sum >> 16);
上一步可能再次产生溢出(高位非0),因此再将高位加回低位。
6. 按位取反
return ~sum;
对最终的结果取反(按位求反)以生成校验和。
IP/TCP/UDP校验和标准规定: 发送方在计算校验和后对结果按位取反,接收方校验时再加和所有字段,结果应为全1。
例子
假设传入数据为:0x4500 0x003c 0x1c46 0x4000 0x4006 0xb1e6
(IPv4头部数据)。
数据分块累加(16位一组):
sum = 0x4500 + 0x003c + 0x1c46 + 0x4000 + 0x4006 + 0xb1e6 = 0x1D5B2
高位与低位相加:
sum = (0x1D5B2 >> 16) + (0x1D5B2 & 0xFFFF) = 0x1 + 0xD5B2 = 0xD5B3
按位取反:
~sum = ~0xD5B3 = 0x2A4C
最终的校验和是:0x2A4C
。
注意事项
字节顺序: 网络协议通常使用大端字节序。如果数据在小端机器上处理,需要注意字节序转换。
用途: 此函数广泛用于IP、TCP、UDP等协议的校验和计算。
效率优化: 在处理大数据时,使用汇编或硬件加速可能更高效。
小结
校验和是通过对数据块进行16位分组累加,然后将溢出部分循环加回,并取反得到的。
这个函数实现了标准的 Internet校验和算法,用于验证数据在传输中是否损坏或被篡改。
C语言位运算
C语言中的位运算是对二进制位进行直接操作的运算,主要用于高效处理数据,常见于底层开发(如驱动开发、嵌入式系统、网络协议等)。以下是详细讲解:
位运算符及作用
运算符 | 名称 | 示例 | 作用 |
---|---|---|---|
& |
按位与(AND) | a & b |
对应位都为 1 时结果为 1 ,否则为 0 。 |
` | ` | 按位或(OR) | `a |
^ |
按位异或(XOR) | a ^ b |
对应位相同结果为 0 ,不同结果为 1 。 |
~ |
按位取反(NOT) | ~a |
将每个位取反,0 变1 ,1 变0 。 |
<< |
左移(Left Shift) | a << n |
将二进制位整体向左移动 n 位,右侧补 0 。等同于乘以 2^n 。 |
>> |
右移(Right Shift) | a >> n |
将二进制位整体向右移动 n 位,左侧补符号位(算术右移)或补 0 (逻辑右移)。等同于除以 2^n (仅适用无符号整数)。 |
每种运算的详细解释和例子
1. 按位与(&)
作用:保留两数的公共位。
#include <stdio.h>
int main() {
unsigned char a = 0b1101; // 13
unsigned char b = 0b1011; // 11
unsigned char result = a & b; // 结果:0b1001 (9)
printf("a & b = %d\n", result);
return 0;
}
二进制表示 | 1 1 0 1 | (a) |
---|---|---|
按位与 | 1 0 1 1 | (b) |
结果 | 1 0 0 1 | (9) |
用途:
- 清零某些位(通过与掩码
0b1111...
操作)。 - 保留特定位的数据。
2. 按位或(|)
作用:将两数的所有位组合起来,只要有一个为 1
,结果就为 1
。
unsigned char a = 0b1101; // 13
unsigned char b = 0b1011; // 11
unsigned char result = a | b; // 结果:0b1111 (15)
printf("a | b = %d\n", result);
二进制表示 | 1 1 0 1 | (a) |
---|---|---|
按位或 | 1 0 1 1 | (b) |
结果 | 1 1 1 1 | (15) |
用途:
- 将某些位设置为
1
。
3. 按位异或(^)
作用:相同为 0
,不同为 1
。
unsigned char a = 0b1101; // 13
unsigned char b = 0b1011; // 11
unsigned char result = a ^ b; // 结果:0b0110 (6)
printf("a ^ b = %d\n", result);
二进制表示 | 1 1 0 1 | (a) |
---|---|---|
按位异或 | 1 0 1 1 | (b) |
结果 | 0 1 1 0 | (6) |
用途:
数据加密(异或加密)。
交换两个数而不使用临时变量:
a = a ^ b; b = a ^ b; a = a ^ b;
4. 按位取反(~)
作用:将每个位取反。
unsigned char a = 0b1101; // 13
unsigned char result = ~a; // 结果:0b0010 (2),注意这是对8位数据操作
printf("~a = %d\n", result);
二进制表示 | 1 1 0 1 | (a) |
---|---|---|
取反 | 0 0 1 0 | (2) |
用途:
- 数据补码表示。
- 快速生成一个反掩码。
5. 左移(<<)
作用:将二进制位向左移动,低位补0。
unsigned char a = 0b0001; // 1
unsigned char result = a << 2; // 结果:0b0100 (4)
printf("a << 2 = %d\n", result);
二进制表示 | 0 0 0 1 | (a) |
---|---|---|
左移 2 位 | 0 1 0 0 | (4) |
用途:
- 快速乘法运算。
- 设置特定位。
6. 右移(>>)
作用:将二进制位向右移动。
unsigned char a = 0b0100; // 4
unsigned char result = a >> 2; // 结果:0b0001 (1)
printf("a >> 2 = %d\n", result);
二进制表示 | 0 1 0 0 | (a) |
---|---|---|
右移 2 位 | 0 0 0 1 | (1) |
用途:
- 快速除法运算。
- 数据对齐操作。
位运算的常见用途
1. 清零/置位
unsigned char a = 0b1010; // 10
a &= ~0b0100; // 清零第3位
printf("a = %d\n", a); // 结果:0b1000 (8)
2. 判断某个位是否为1
unsigned char a = 0b1010;
if (a & 0b0010) {
printf("第2位是1\n");
}
3. 提取某些位
unsigned char a = 0b11010110; // 提取中间4位
unsigned char result = (a >> 2) & 0b1111;
printf("result = %d\n", result); // 结果:13
4. 交换高低字节
unsigned short x = 0xABCD;
x = (x >> 8) | (x << 8); // 高低字节交换
printf("x = 0x%X\n", x); // 结果:0xCDAB
5. 加密与解密
unsigned char data = 0b1101; // 加密
unsigned char key = 0b1010;
unsigned char encrypted = data ^ key;
unsigned char decrypted = encrypted ^ key;
printf("decrypted = %d\n", decrypted); // 解密回原值
小结
- 位运算符直接操作二进制位,效率高。
- 常见用途包括清零、置位、数据加密、掩码操作等。
- 掌握位运算是深入理解计算机底层操作的基础技能。
代码片段:
// UDP 校验和计算函数
unsigned short udp_checksum(struct iphdr *ip, struct udphdr *udp, char *payload, int payload_len) {
struct {
uint32_t src_addr;
uint32_t dest_addr;
uint8_t reserved;
uint8_t protocol;
uint16_t udp_length;
} pseudo_header;
pseudo_header.src_addr = ip->saddr;
pseudo_header.dest_addr = ip->daddr;
pseudo_header.reserved = 0;
pseudo_header.protocol = IPPROTO_UDP;
pseudo_header.udp_length = htons(sizeof(struct udphdr) + payload_len);
char buffer[4096];
memcpy(buffer, &pseudo_header, sizeof(pseudo_header));
memcpy(buffer + sizeof(pseudo_header), udp, sizeof(struct udphdr));
memcpy(buffer + sizeof(pseudo_header) + sizeof(struct udphdr), payload, payload_len);
return checksum(buffer, sizeof(pseudo_header) + sizeof(struct udphdr) + payload_len);
}
UDP 校验和计算函数讲解
此函数实现了计算UDP数据报的校验和,符合RFC 768协议的要求。UDP校验和不仅校验UDP报文,还需要包括一个伪首部,伪首部包含IP报头中的部分信息,用于加强校验的可靠性。
函数功能
udp_checksum
函数根据UDP报文的内容和伪首部计算16位的UDP校验和。该校验和用于检测数据传输中的错误。
参数说明
unsigned short udp_checksum(struct iphdr *ip, struct udphdr *udp, char *payload, int payload_len)
struct iphdr *ip
: 指向IP头部结构的指针,提供源IP地址和目标IP地址。struct udphdr *udp
: 指向UDP头部结构的指针,提供UDP报文信息。char *payload
: 指向UDP数据部分的指针。int payload_len
: UDP数据部分的长度(以字节为单位)。
返回值:16位的UDP校验和。
核心步骤解析
1. 构造伪首部
UDP校验和需要将伪首部与UDP报文拼接在一起计算校验和。伪首部的结构如下:
struct {
uint32_t src_addr; // 源IP地址
uint32_t dest_addr; // 目标IP地址
uint8_t reserved; // 保留位,设置为0
uint8_t protocol; // 协议号 (UDP = 17)
uint16_t udp_length; // UDP报文总长度(UDP头部 + 数据部分)
} pseudo_header;
以下是伪首部的填充代码:
pseudo_header.src_addr = ip->saddr; // 设置源IP地址
pseudo_header.dest_addr = ip->daddr; // 设置目标IP地址
pseudo_header.reserved = 0; // 保留位为0
pseudo_header.protocol = IPPROTO_UDP; // 协议号(UDP = 17)
pseudo_header.udp_length = htons(sizeof(struct udphdr) + payload_len); // UDP长度,需使用网络字节序
2. 构造待计算校验和的数据
计算校验和的数据由三部分组成:
- 伪首部。
- UDP头部。
- UDP数据(payload)。
以下代码将三部分数据拼接到缓冲区 buffer
中:
memcpy(buffer, &pseudo_header, sizeof(pseudo_header)); // 添加伪首部
memcpy(buffer + sizeof(pseudo_header), udp, sizeof(struct udphdr)); // 添加UDP头部
memcpy(buffer + sizeof(pseudo_header) + sizeof(struct udphdr), payload, payload_len); // 添加UDP数据
3. 调用 checksum
计算校验和
checksum
函数负责按位计算校验和,代码如下:
return checksum(buffer, sizeof(pseudo_header) + sizeof(struct udphdr) + payload_len);
buffer
: 包含伪首部、UDP头部和UDP数据的缓冲区。sizeof(pseudo_header) + sizeof(struct udphdr) + payload_len
: 校验和计算的总数据长度。
核心函数的细节
伪首部的作用:
- 增加IP地址、协议号和长度的信息,确保UDP报文的校验更全面。
- 伪首部不会实际发送到网络中,仅用于校验。
网络字节序:
- IP和UDP字段中的多字节值必须是网络字节序(大端)。
- 函数使用了
htons
将长度转换为网络字节序。
缓冲区限制:
- 缓冲区
buffer[4096]
假设数据不会超过4096字节(通常足够),但在实际使用中应确保数据大小不会超出限制。
- 缓冲区
数据流程图
假设有以下数据:
- IP头部:
struct iphdr
包含源地址192.168.1.1
和目标地址192.168.1.2
。 - UDP头部:
struct udphdr
包含源端口12345
和目标端口80
。 - UDP数据:
"Hello, UDP!"
,长度12
。
数据流如下:
伪首部:
+--------------------+--------------------+-----+-----+-----+
| 源IP地址 | 目标IP地址 | 0 | 17 | 20 |
+--------------------+--------------------+-----+-----+-----+
UDP头部:
+----------------+----------------+----------------+----------------+
| 源端口 | 目标端口 | 长度(20) | 校验和(空) |
+----------------+----------------+----------------+----------------+
UDP数据:
+----------------+
| Hello, UDP! |
+----------------+
拼接后调用 checksum
计算校验和。
示例调用
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
// 假设 checksum 函数已定义
int main() {
struct iphdr ip;
struct udphdr udp;
char payload[] = "Hello, UDP!";
int payload_len = strlen(payload);
ip.saddr = inet_addr("192.168.1.1");
ip.daddr = inet_addr("192.168.1.2");
udp.source = htons(12345);
udp.dest = htons(80);
udp.len = htons(sizeof(struct udphdr) + payload_len);
udp.check = 0;
unsigned short checksum = udp_checksum(&ip, &udp, payload, payload_len);
printf("UDP校验和: 0x%04x\n", checksum);
return 0;
}
小结
- 伪首部是UDP校验和的重要组成部分,用于校验IP和协议相关信息。
- 函数通过拼接伪首部、UDP头部和数据,构造完整的校验和输入。
- 使用标准的
checksum
函数对拼接的数据进行计算,确保符合UDP协议标准。
你可以将这段代码与网络编程任务结合,检查UDP报文的校验和是否正确。
代码片段:
// 构造IP头
struct iphdr *ip = (struct iphdr *)buffer;
ip->ihl = 5; // Header length
ip->version = 4; // IPv4
ip->tos = 0; // Type of service
ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + 12); // IP头 + UDP头 + 数据长度
ip->id = htons(54321); // Packet ID
ip->frag_off = 0; // No fragmentation
ip->ttl = 64; // Time to live
ip->protocol = IPPROTO_UDP; // UDP协议
ip->saddr = inet_addr("192.168.122.85"); // 源IP地址
ip->daddr = inet_addr("8.8.8.8"); // 目标IP地址
ip->check = checksum((unsigned short *)ip, sizeof(struct iphdr)); // IP头校验和
这段代码展示了如何在C语言中使用原始套接字(raw socket)构造并发送自定义的UDP数据包,以下是分步骤详细讲解:
1. 数据包缓冲区初始化
char buffer[4096];
memset(buffer, 0, sizeof(buffer));
- 定义一个4096字节的缓冲区
buffer
,用于存放完整的数据包(包括IP头、UDP头和数据负载)。 - 使用
memset
将缓冲区初始化为全零,以避免未初始化数据影响数据包内容。
2. 创建原始套接字
int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
- 使用
socket
函数创建一个原始套接字。AF_INET
: 指定IPv4协议。SOCK_RAW
: 表示使用原始套接字,可以自定义IP和传输层协议头部。IPPROTO_UDP
: 指定使用UDP协议。
- 如果创建失败(
sockfd < 0
),程序会打印错误信息并退出。
3. 设置 IP_HDRINCL
选项
int one = 1;
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one));
- 设置套接字选项
IP_HDRINCL
,表示自己构造IP头部,操作系统不会自动添加。 - 如果
setsockopt
失败,程序会打印错误信息并退出。
4. 构造IP头部
struct iphdr *ip = (struct iphdr *)buffer;
ip->ihl = 5; // IP头部长度(单位为4字节,5表示20字节)
ip->version = 4; // IPv4协议
ip->tos = 0; // 服务类型
ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + 12); // 总长度
ip->id = htons(54321); // 数据包ID
ip->frag_off = 0; // 不分片
ip->ttl = 64; // 生存时间
ip->protocol = IPPROTO_UDP; // 协议类型为UDP
ip->saddr = inet_addr("192.168.122.85"); // 源IP地址
ip->daddr = inet_addr("8.8.8.8"); // 目标IP地址
ip->check = checksum((unsigned short *)ip, sizeof(struct iphdr)); // IP头校验和
- IP头部字段解释:
ihl
和version
:分别指定IP头长度(20字节)和IPv4协议版本。tot_len
:IP包总长度,包括IP头部、UDP头部和数据部分。id
:数据包标识符,用于分片时重组。frag_off
:分片偏移,设置为0表示不分片。ttl
:生存时间,用于限制数据包在网络中的存活跳数。protocol
:协议类型,IPPROTO_UDP
表示UDP。saddr
和daddr
:分别为源IP和目标IP地址。check
:IP头校验和,用于确保IP头部数据正确。
5. 构造UDP头部
struct udphdr *udp = (struct udphdr *)(buffer + sizeof(struct iphdr));
udp->source = htons(12345); // 源端口
udp->dest = htons(53); // 目标端口
udp->len = htons(sizeof(struct udphdr) + 12); // UDP头部长度 + 数据长度
udp->check = 0; // 初始校验和设为0
- UDP头部字段解释:
source
和dest
:分别为源端口和目标端口。len
:UDP报文的总长度,包括UDP头部和数据部分。check
:UDP校验和,初始化为0,后续会计算。
6. 填充数据负载
char *payload = buffer + sizeof(struct iphdr) + sizeof(struct udphdr);
strcpy(payload, "Hello, UDP!");
- 数据负载部分存储在IP头部和UDP头部之后。
- 示例中,数据为
"Hello, UDP!"
(12字节)。
7. 计算UDP校验和
udp->check = udp_checksum(ip, udp, payload, strlen(payload));
- 调用之前定义的
udp_checksum
函数计算校验和,并将结果存入udp->check
字段。
8. 目标地址设置
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_addr.s_addr = ip->daddr;
- 定义目标地址结构
sockaddr_in
:sin_family
:协议族,IPv4。sin_addr.s_addr
:目标IP地址,与IP头部中的daddr
一致。
9. 发送数据包
sendto(sockfd, buffer, ntohs(ip->tot_len), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
使用
sendto
函数发送数据包:sockfd
:原始套接字。buffer
:包含数据包的缓冲区。ntohs(ip->tot_len)
:数据包总长度。(struct sockaddr *)&dest_addr
:目标地址。sizeof(dest_addr)
:目标地址结构大小。
如果发送失败,会打印错误信息;如果成功,打印发送成功的消息。
10. 关闭套接字
close(sockfd);
- 发送完成后,关闭套接字,释放资源。
程序执行流程总结
- 创建并配置原始套接字。
- 构造IP头部、UDP头部和数据负载。
- 计算IP校验和和UDP校验和。
- 使用
sendto
函数发送数据包到目标地址。
注意事项
权限要求:
- 原始套接字需要管理员权限(Linux系统需要
sudo
执行)。
- 原始套接字需要管理员权限(Linux系统需要
伪头部的正确性:
- 伪头部中的源IP和目标IP地址必须与IP头部一致,否则校验和会错误。
IP分片:
- 此示例中未处理分片。如果数据包过大,可能需要分片发送。
调试工具:
- 可以使用
tcpdump
或wireshark
检查发送的数据包是否正确。
- 可以使用
示例输出
假设成功运行程序,可能的输出为:
UDP Packet sent successfully!
鸣谢:感谢ChatGPT提供技术支持