08_网络编程-UDP
第四章 UDP 通讯
4.1 相关函数介绍
sendto() 发送数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
/*
功能:
发送数据到指定的目标地址。常用于UDP协议的无连接通信。
参数:
sockfd: 套接字描述符,指向已经创建的UDP套接字。
buf: 指向要发送数据的缓冲区。
len: 要发送的数据长度,以字节为单位。
flags: 发送标志,通常设为0。
dest_addr: 目标地址结构体的指针,指定接收数据的目标地址。
addrlen: 目标地址结构体的长度。
返回值:
成功: 返回发送的字节数。
失败: 返回 -1,并设置 errno 以指示错误类型。
*/
recvfrom() 接收数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
/*
功能:
从指定的套接字接收数据,并可以获取发送方的地址信息。常用于UDP协议的无连接通信。
参数:
sockfd: 套接字描述符,指向已经创建的UDP套接字。
buf: 指向接收数据的缓冲区。
len: 缓冲区的大小,以字节为单位。
flags: 接收标志,通常设为0。
src_addr: 发送方地址结构体的指针,用于存储发送方的地址信息。
addrlen: 发送方地址结构体的长度指针,接收后存储实际地址长度。
返回值:
成功: 返回接收的字节数。
失败: 返回 -1,并设置 errno 以指示错误类型。
*/
4.2 UDP 开发流程和示例程序
[!WARNING]
下面图有问题 ==服务器== 和 ==客户端== 图标放反了
服务器流程
1、创建用户数据报 套接字
2、填充服务器网络信息结构体
3、将套接字与服务器的网络信息结构体绑定
4、收发数据
5、关闭套接字
客户端
1、创建用户数据报 套接字
2、填充服务器网络信息结构体
3、收发数据
4、关闭套接字
服务器
// udp_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080 // 服务器监听的端口
#define BUFFER_SIZE 1024 // 缓冲区大小
int main() {
int sockfd;
char buffer[BUFFER_SIZE];
struct sockaddr_in servaddr, cliaddr;
// 创建套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 清空并设置服务器地址
memset(&servaddr, 0, sizeof(servaddr));
memset(&cliaddr, 0, sizeof(cliaddr));
// 设置服务器信息
servaddr.sin_family = AF_INET; // IPv4
servaddr.sin_addr.s_addr = INADDR_ANY; //
// 绑定套接字到地址
if (bind(sockfd, (const struct sockaddr *)&servaddr,
sizeof(servaddr)) < 0) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("UDP 服务器正在监听端口 %d...\n", PORT);
int len, n;
len = sizeof(cliaddr); // 必须初始化
// 接收来自客户端的数据
n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, 0,
(struct sockaddr *)&cliaddr, &len);
buffer[n] = '\0';
printf("从客户端收到: %s\n", buffer);
// 准备发送的数据
const char *hello = "Hello from UDP server";
// 发送数据给客户端
sendto(sockfd, (const char *)hello, strlen(hello), 0,
(const struct sockaddr *)&cliaddr, len);
printf("发送消息给客户端: %s\n", hello);
// 关闭套接字
close(sockfd);
return 0;
}
客户端
// udp_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080 // 服务器的端口
#define BUFFER_SIZE 1024 // 缓冲区大小
int main() {
int sockfd;
char buffer[BUFFER_SIZE];
const char *hello = "Hello from UDP client";
struct sockaddr_in servaddr;
// 创建套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("创建套接字失败");
exit(EXIT_FAILURE);
}
// 清空并设置服务器地址
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET; // IPv4
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = INADDR_ANY;
int n, len;
// 发送数据给服务器
sendto(sockfd, (const char *)hello, strlen(hello), 0,
(const struct sockaddr *)&servaddr, sizeof(servaddr));
printf("发送消息给服务器: %s\n", hello);
// 接收来自服务器的数据
len = sizeof(servaddr);
n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, 0,
(struct sockaddr *)&servaddr, &len);
buffer[n] = '\0';
printf("从服务器收到: %s\n", buffer);
// 关闭套接字
close(sockfd);
return 0;
}
4.2 TFTP 文件协议
简单文件传输协议 用于设计传输小文件
特点
基于UDP 协议实现
不进行用于的有效性认证
数据传输模式
octet 二进制模式
netascii 文本模式
mail 不支持了
TFTP 协议分析
这是一个描述TFTP(Trivial File Transfer Protocol)数据包结构的图。TFTP是一种简单的文件传输协议,通常在较低资源设备或简单的网络文件传输场景下使用。
TFTP数据包类型及其结构
读写请求(RRQ/WRQ)数据包
- IP头部(20字节):IP数据报的首部,包含源地址、目的地址等信息。
- UDP头部(8字节):UDP数据报的首部,包含源端口、目的端口等信息。
- 操作码(Opcode)(2字节):操作码1表示读请求(RRQ),操作码2表示写请求(WRQ)。
- 文件名(N字节):要读取或写入的文件名,以字节形式表示。
- 0字节终止符(1字节):用于分隔文件名和传输模式。
- 模式(N字节):表示传输模式,通常是“netascii”(文本模式)、“octet”(二进制模式)或“mail”。
- 0字节终止符(1字节):用于标识模式的结束。
数据包(DATA)
- 用于传输文件内容的数据。
- 结构:
- 操作码(Opcode)(2字节):固定值3,表示数据包。
- 块编号(2字节):当前数据块的编号,从1开始递增,用于确保数据包顺序正确。
- 数据(512字节):实际传输的数据内容,每个数据包最多传输512字节。
确认包(ACK)
- 用于确认接收到的数据包。
- 结构:
- 操作码(Opcode)(2字节):固定值4,表示确认包。
- 块编号(2字节):确认的块编号,与收到的数据包的块编号一致。
错误包(ERROR)
- 用于指示传输过程中发生的错误。
- 结构:
- 操作码(Opcode)(2字节):固定值5,表示错误包。
- 错误代码(2字节):表示特定错误类型的代码,例如文件未找到、访问权限错误等。
- 错误信息(N字节):可变长度的错误描述信息,帮助诊断问题。
- 0字节终止符(1字节):标识错误信息的结束。
数据包传输过程
- TFTP数据包传输使用UDP协议(User Datagram Protocol),因为UDP是一种无连接、不可靠的传输协议,传输效率较高,但需要由TFTP协议本身负责数据重传和顺序控制。
- 一个TFTP会话通常以读或写请求(RRQ/WRQ)开始,服务器和客户端通过传输数据包(DATA)和确认包(ACK)来交换数据。任何错误将通过错误包(ERROR)来通知对方。
这种数据结构使TFTP非常简单,适用于传输小文件或在内存和计算能力有限的设备上进行数据传输。
组装请求的方法
char buf[128];
char file_name[128];
组装方式1 通过改变指针的操作空间
*(unsigned char *)buf = htons(1);
组装方式2 对每个字节进行写入 注意 需要使用网络字节序
buff[0] = 0;
buff[1] = 1;
组装方式3 sprintf
sprintf(buf , "%c%c%S%c%s%c" , 0 , 1 , file_name , 0 , "octet" , 0);
客户端示例代码
#include <head.h>
#define TFTP_MODE "octet" // 操作模式
// TFTP 操作码
#define RRQ 1
#define DATA 3
#define ACK 4
#define ERROR 5
#define BUF_MAX 600
// UDP 通讯初始化
int UDP_Client_Init(struct sockaddr_in *UDP_addr, const char *IP_val, in_port_t port);
// 终端输入 IP地址 + 端口
int main(int argc, char const *argv[])
{
// 终端输入 判定
ARGV_IF(argc, 3);
// UDP 通讯初始化
struct sockaddr_in UDP_addr;
socklen_t UDP_len = sizeof(UDP_addr);
int Fd_udp_soc = UDP_Client_Init(&UDP_addr, argv[1], atoi(argv[2]));
/**************************** TFTP 客户端*****************************/
char buf[600];
char File_name[64];
int buf_len;
unsigned short code; // 操作码
unsigned short num; // 块编号
char txt_data[516];
// 接收结构体
struct sockaddr_in From_addr;
socklen_t From_len = sizeof(From_addr);
// 用户输入文件名
printf("输入你要下载的文件名\n");
scanf("%s", File_name);
int fd_file = open(File_name , O_WRONLY | O_TRUNC | O_CREAT , 0666);
// 发送请求信号
buf_len = sprintf(buf, "%c%c%s%c%s%c", 0, 1, File_name, 0, TFTP_MODE, 0);
sendto(Fd_udp_soc, buf, buf_len, 0, (struct sockaddr *)&UDP_addr, UDP_len);
while (1)
{
// 接收 解析数据
buf_len = recvfrom(Fd_udp_soc, buf, BUF_MAX, 0, (struct sockaddr *)&From_addr, &From_len);
code = ntohs(*(unsigned short *)buf); // 获取 操作码
num = ntohs(*(unsigned short *)(buf + 2)); // 获取 块编号
strncpy(txt_data, buf + 4, buf_len - 4); // 获取 数据
printf("\n接收数据:%hu %hu %s\n", code, num, txt_data);
if (DATA == code)
{
// 将数据写入到文件中
write( fd_file , buf + 4 , buf_len - 4);
if ((buf_len - 4) < 512)
{
printf("传输完成\n");
break;
}
// 构建应答信号
memset(buf, 0, sizeof(buf));
*((unsigned short *)buf) = htons(ACK); // 将操作码转换为网络字节序
*((unsigned short *)(buf + 2)) = htons(num); // 将块编号转换为网络字节序
// 发送应答信息
sendto(Fd_udp_soc, buf, 4, 0, (struct sockaddr *)&From_addr, From_len);
}
}
/**************************** TFTP 客户端 *****************************/
// 4、关闭套接字.
close(fd_file);
close(Fd_udp_soc);
return 0;
}
int UDP_Client_Init(struct sockaddr_in *UDP_addr, const char *IP_val, in_port_t port)
{
// 1、创建用户数据报 套接字
int Fd_udp_soc = 0;
if (-1 == (Fd_udp_soc = socket(AF_INET, SOCK_DGRAM, 0)))
{
ERRLOG("socket");
}
// 2、填充服务器网络信息结构体
UDP_addr->sin_family = AF_INET;
UDP_addr->sin_port = htons(port);
UDP_addr->sin_addr.s_addr = inet_addr(IP_val);
// 连接测试
// sendto( Fd_udp_soc , "UDP-连接成功\n" , strlen("UDP-连接成功\n") , 0 , (struct sockaddr *)UDP_addr , sizeof(struct sockaddr_in) );
return Fd_udp_soc;
}
4.4 抓包问题
4.4.1 Wireshark 安装问题
windows 安装
鼠标右键点击 Wireshark-win64-3.2.2.exe,以管理员身份运行,
选择安装路径后默认下一步即可,如果提示缺少插件,就都勾选安装即可,尤其是有一页会出现 usbpcapxx 一定要安装,
最后一页有一个 立即重启 (reboot now) 除了这个都勾选。
Ubuntu 安装
sudo apt-get install wireshark
无法获取无线网卡的办法
管理员 用户打开 cmd
输入 net start npf
重启 Wireshark 以管理员方式打开
如果不行 检查 npcap 状态
管理员输入
检查状态
sc query npcap
如果状态显示 STATE : 1 STOPED
则代表没有启动
启动方式
sc start npcap
当可以看到网卡信息 说明成功了 如下
当有网卡下有波浪线的时候代表 已经有数据进行了收发
在这里选择 我们需要的网卡
有数据包界面
常用的过滤
tcp.port==8888 查端口
ip.src==192.168.0.1 查询 源 IP 地址
ip.dst==192.168.0.1 查询 目标地址 IP 地址
可以使用 and 和 or 连接多个语句
4.4.2 软件说明
开始捕获 和 停止捕获
No. 编号
Time 时间
Source 源 IP 地址
Destination 目标IP地址
Protocol 协议
Length 长度
Info 包的简要说明
Ethernet 以太网头
Internet Protool IP头信息 网络层信息
Transmission Control Protool TCP头信息 传输层信息
信息说明
4.4.3 链路层分析
MAC 头部协议
mac帧头部占14字节(6+6+2,目的mac+源mac+类型) ,
Frame 650: 194 bytes on wire (1552 bits), 194 bytes captured (1552 bits) on interface \Device\NPF_{6A122268-C6CF-44ED-98DC-85AF5A01CB95}, id 0
Ethernet II, Src: Intel_74:74:30 (a0:59:50:74:74:30), Dst: Intel_74:74:30 (a0:59:50:74:74:30)
Destination: Intel_74:74:30 (a0:59:50:74:74:30)
Source: Intel_74:74:30 (a0:59:50:74:74:30)
Type: IPv4 (0x0800)
[Stream index: 1]
Internet Protocol Version 4, Src: 172.20.10.9, Dst: 172.20.10.3
Transmission Control Protocol, Src Port: 35514, Dst Port: 6666, Seq: 1, Ack: 1, Len: 128
目的mac 地址
Destination: Intel_74:74:30 (a0:59:50:74:74:30)
源 mac 地址
Source: Intel_74:74:30 (a0:59:50:74:74:30)
使用 的IP 协议
Type: IPv4 (0x0800)
4.4.4 网络层数据
IP头数据分析
版本: 这是IP协议的版本号,占4个比特位。4表示IPv4协议,6表示IPv6协议。
首部长度: IP头部的长度,占4个比特位。这个字段表示IP头部的长度,以4字节为单位。最大值为15(1111),表示60字节。
服务类型:服务类型字段用于定义数据包的优先级和服务质量。8个比特位通常分为三个部分:优先级(Precedence)、延迟(Delay)、吞吐量(Throughput)、可靠性(Reliability)。
总长度: IP头 + TCP头 + 用户数据 的长度
标识:一个唯一的标识符,占16个比特位,用于识别数据包。在数据包分片时,所有分片具有相同的标识符,以便接收端可以重新组装。
标志:占3个比特位,用于控制和指示分片。包括3个标志位:
第一个比特未使用,总是0。
第二个比特为“不分片位(DF)”,如果为1,表示数据包不可分片。
第三个比特为“更多分片位(MF)”,如果为1,表示数据包后面还有分片。
片偏移:占13个比特位,表示当前数据包片相对于原始数据包的偏移量(单位为8字节)。用于分片重组。
生存时间: 占8个比特位,用于限制数据包的生命周期,通常表示数据包可以经过的路由器的最大数量。每经过一个路由器,TTL减1,TTL为0时,数据包被丢弃。
协议类型: 占8个比特位,表示上层协议的类型。例如,6表示TCP,17表示UDP。
头部校验和: 占16个比特位,用于校验IP头部的完整性。发送端计算校验和并填充,接收端进行校验,校验失败则丢弃数据包。
源IP 地址:占32个比特位,表示数据包的发送方的IP地址。
目的IP 地址:占32个比特位,表示数据包的接收方的IP地址。
抓包信息
Frame 650: 194 bytes on wire (1552 bits), 194 bytes captured (1552 bits) on interface \Device\NPF_{6A122268-C6CF-44ED-98DC-85AF5A01CB95}, id 0
Ethernet II, Src: Intel_74:74:30 (a0:59:50:74:74:30), Dst: Intel_74:74:30 (a0:59:50:74:74:30)
Destination: Intel_74:74:30 (a0:59:50:74:74:30)
Source: Intel_74:74:30 (a0:59:50:74:74:30)
Type: IPv4 (0x0800)
[Stream index: 1]
Internet Protocol Version 4, Src: 172.20.10.9, Dst: 172.20.10.3
0100 .... = Version: 4
.... 0101 = Header Length: 20 bytes (5)
Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
Total Length: 180
Identification: 0x92b0 (37552)
010. .... = Flags: 0x2, Don't fragment
...0 0000 0000 0000 = Fragment Offset: 0
Time to Live: 64
Protocol: TCP (6)
Header Checksum: 0x3b5f [validation disabled]
[Header checksum status: Unverified]
Source Address: 172.20.10.9
Destination Address: 172.20.10.3
[Stream index: 1]
Transmission Control Protocol, Src Port: 35514, Dst Port: 6666, Seq: 1, Ack: 1, Len: 128
交换机是工作在链路层的设备 是根据 MAC地址来决定如何转发信息
路由器是工作在网络层的设备 是根据 IP地址来决定如何转发信息
4.4.5 传输层
源端口: 发送方的端口 (随机的应为没有绑定)
目的端口: 接收方的端口
顺序号:seq
确认号:ack
头部长度: 也是 4倍的单位 5 表示 20
```
Source Port (源端口): 35514
Destination Port (目标端口): 6666
Stream Index (流索引): 5
Stream Packet Number (流数据包编号): 7
Conversation Completeness (会话完整性): Complete, WITH_DATA (31)
TCP Segment Length (TCP段长度): 128
Sequence Number (序列号): 1 (relative sequence number) 相对序列号
Sequence Number (raw) (原始序列号): 2568202456
Next Sequence Number (下一个序列号): 129 (relative sequence number) 相对序列号
Acknowledgment Number (确认号): 1 (relative ack number) 相对确认号
Acknowledgment Number (raw) (原始确认号): 2104686676
Header Length (头部长度): 32 bytes (8)
Flags (标志位): 0x018 (PSH, ACK)
Window (窗口大小): 229
Calculated Window Size (计算得出的窗口大小): 29312
Window Size Scaling Factor (窗口大小缩放因子): 128
Checksum (校验和): 0x25f8 [unverified] 未验证
Checksum Status (校验和状态): Unverified 未验证
Urgent Pointer (紧急指针): 0
Options (选项): (12 bytes), No-Operation (NOP), No-Operation (NOP), No-Operation (NOP), Timestamps (时间戳)
TCP Option - No-Operation (NOP) 空操作
TCP Option - No-Operation (NOP) 空操作
TCP Option - Timestamps (时间戳): TSval 1045131, TSecr 279527307
Timestamps (时间戳):
Time since first frame in this TCP stream (从此TCP流的第一个数据包开始的时间): 2.195243000 seconds
Time since previous frame in this TCP stream (从上一个数据包开始的时间): 2.194942000 seconds
SEQ/ACK Analysis (序列/确认分析):
iRTT (初始往返时间): 0.000287000 seconds
Bytes in flight (未被确认的字节数): 128
Bytes sent since last PSH flag (自上次PSH标志后发送的字节数): 128
TCP Payload (TCP负载): 128 bytes
4.4.6 应用层
数据如下
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 小道士