2024-02-29-Linux高级网络编程(3-UDP编程-TFTP、广播、多播)
3. UDP编程-TFTP、广播、多播
3.1 TFTP简介、通信过程
3.1.1 TFTP概述
TFTP:简单文件传送协议(Trivial File Transfer Protocol),最初用于引导无盘系统,被设计用来传输小文件
特点:基于UDP实现,不进行用户有效性认证
数据传输模式:
- octet:二进制模式
- netascii:文本模式
- mail:已经不再支持
3.1.2 TFTP通讯过程

TFTP通信过程总结
- 服务器在
69号端口
等待客户端的请求 - 服务器若批准此请求,则使用临时端口与客户端进行通信
- 每个数据包的编号都有变化(从1开始)
- 每个数据包都要得到ACK的确认如果出现超时,则需要重新发送最后的包(数据或ACK)
- 数据的长度以
512 Bytes
传输 - 小于
512Bytes
的数据意味着传输结束
3.1.3 TFTP协议分析

注意:
1. 以上的0代表的是\0
1. 不同的差错码对应不同的错误信息
3.1.4 案例
略
3.2 UDP广播
3.2.1 广播的概念
广播:由一台主机向该主机所在子网内的所有主机发送数据的方式;例如192.168.3.103
主机发送广播信息,则·192.168.3.1~192.168.3.254·所有主机都可以接收到数据
广播只能用UDP或原始IP实现,不能用TCP
3.2.2 广播的用途
单个服务器与多个客户主机通信时减少分组流通以下几个协议都用到广播
- 地址解析协议(ARP.)
- 动态主机配置协议(DHCP )
- 网络时间协议(NTP)
3.2.3 广播的特点
- 处于同一子网的所有主机都必须处理数据
- UDP数据包会沿协议栈向上一直到UDP层
- 运行音视频等较高速率工作的应用,会带来大负载
- 局限于局域网内使用
3.2.4 广播地址
IP格式:{网络ID,主机ID}
网络ID表示由子网掩码中1覆盖的连续位;主机ID表示由子网掩码中0覆盖的连续位
定向广播地址:主机ID全1
- 例:对于
192.168.220.0/24
,其定向广播地址为192.168.220.255
- 通常路由器不转发该广播
受限广播地址:255.255.255.255
路由器从不转发该广播
3.2.5 广播流程
发送者:
- 第一步,创建套接字 socket()
- 第二步,设置运行发送广播权限setsockopt()
- 第三步,向广播地址发送数据sendto()
接收者:
- 第一步,创建套接字 socket()
- 第二步,将套接字与广播的信息结构体绑定bind()
- 第三步,接收数据recvfrom()
3.2.6 套接口选项
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); 功能:设置一个套接字的选项(属性); 参数: socket:文件描述符; level:协议层次 SOL_SOCKET 套接字层次 IPPROTO TCP tcp层次 IPPROTO IP IP层次 option_name: 选项的名称 SO_BROADCAST 允许发送广播数据 (SOL_SOCKET层次的) option_value: 设置的选项的值,设置是否允许发送前面套接字的值 int类型的值,存储的是bool的数据(1和0) 0 不允许 1 允许 option_len: option_value的长度 返回值: 成功0 失败-1
3.2.7 广播案例
broadcast_send.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define DEFAULT_PORT 32345 #define BROADCAST_ADDRESS "255.255.255.255" int main() { int sockfd; struct sockaddr_in broadcast_addr; int broadcast_enable = 1; // 创建套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { perror("socket"); exit(1); } // 设置套接字选项,允许广播 if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast_enable, sizeof(broadcast_enable)) == -1) { perror("setsockopt"); exit(1); } // 设置广播地址 memset(&broadcast_addr, 0, sizeof(broadcast_addr)); broadcast_addr.sin_family = AF_INET; broadcast_addr.sin_port = htons(DEFAULT_PORT); if (inet_pton(AF_INET, BROADCAST_ADDRESS, &(broadcast_addr.sin_addr)) <= 0) { perror("inet_pton"); exit(1); } // 构造广播数据 char *message = "Hello, broadcast!"; int message_len = strlen(message); // 发送广播 if (sendto(sockfd, message, message_len, 0, (struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr)) == -1) { perror("sendto"); exit(1); } printf("Broadcast message sent.\n"); // 关闭套接字 close(sockfd); return 0; }
broadcast_recv.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define DEFAULT_PORT 32345 int main() { int sockfd; struct sockaddr_in server_addr; struct sockaddr_in client_addr; socklen_t client_addr_len; char buffer[1024]; // 创建套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { perror("socket"); exit(1); } // 设置服务器地址 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(DEFAULT_PORT); // 绑定套接字到服务器地址 if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("bind"); exit(1); } printf("Waiting for broadcast message...\n"); while (1) { // 接收广播消息 client_addr_len = sizeof(client_addr); int recv_len = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&client_addr, &client_addr_len); if (recv_len == -1) { perror("recvfrom"); exit(1); } buffer[recv_len] = '\0'; printf("Received broadcast message from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); printf("Message: %s\n", buffer); } // 关闭套接字 close(sockfd); return 0; }
3.3 UDP多播
3.3.1 多播的概念
多播: 数据的收发仅仅在同一分组中进行
多播的特点
- 多播地址标示一组接口
- 多播可以用于广域网使用
- 在IPv4中,多播是可选的
多播(Multicast)地址是一种特殊的IP地址,用于在多播通信中标识一组接收者。
多播地址范围是224.0.0.0 ~ 239.255.255.255
。
其中,224.0.0.0
到224.0.0.255
是保留的用于特殊目的的多播地址范围,称为本地链路多播地址。
从224.0.1.0
到238.255.255.255
是全球范围的多播地址范围,称为全局多播地址。
239.0.0.0
到239.255.255.255
是管理员可用的本地多播地址范围。
3.3.2 多播流程
发送者:
- 第一步,创建套接字 socket()
- 第三步,向多播地址发送数据sendto()
接收者:
- 第一步,创建套接字 socket()
- 第二步,设置为加入多播组setsockopt()
- 第三步,将套接字与多播信息结构体绑定bind()
- 第四步,接收数据recvfrom()
3.3.3 多播地址结构体
在IPv4因特网域(AF_INET)中,多播地址结构体用如下结构体ip_mreq
表示
struct in_addr{ in_addr_t s_addr; } struct ip_mreq{ struct in_addr imr_multiaddr; //多播组IP,如224.0.0.1 struct in_addr imr_interface; //将要添加到多播组的IP,可以用INADDR_ANY }
3.3.4 多播套接口选项
#include <sys/socket.h> int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); 功能:设置一个套接字的选项(属性); 参数: socket:文件描述符 level:协议层次 IPPROTO IP层次 option_name:选项的名称 IP_ADDIMEMBERSHIP 加入多播组 option_value:设置的选项的值 struct ip_mreq{ struct in_addr imr_multiaddr; //组播ip地址 struct in_addr imr_interface; //主机地址, INADDR_ANY 任意主机地址(自动获取你的主机地址) } option_len:option_value的长度; 返回值: 成功:0 失败:-1
加入多播组示例
char group[INET_ADDRSTRLEN] = "224.0.0.1"; //定义一个多播地址 struct ip_mreq mreq; //添加一个多播组IP mreq.imr_interface.s_addr = inet_addr(group); //添加一个将要添加到多播组的IP mreq.imr_interface.s_addr =htonl(INADDR_ANY); setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
10_groupcast_send.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define DEFAULT_PORT 12346 #define MULTICAST_ADDRESS "239.0.0.1" int main() { int sockfd; struct sockaddr_in multicast_addr; char *message = "Hello, multicast!"; int message_len = strlen(message); // 创建套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { perror("socket"); exit(1); } // 设置多播地址 memset(&multicast_addr, 0, sizeof(multicast_addr)); multicast_addr.sin_family = AF_INET; multicast_addr.sin_port = htons(DEFAULT_PORT); if (inet_pton(AF_INET, MULTICAST_ADDRESS, &(multicast_addr.sin_addr)) <= 0) { perror("inet_pton"); exit(1); } // 发送多播数据 if (sendto(sockfd, message, message_len, 0, (struct sockaddr *)&multicast_addr, sizeof(multicast_addr)) == -1) { perror("sendto"); exit(1); } printf("Multicast message sent.\n"); // 关闭套接字 close(sockfd); return 0; }
11_groupcast_recv.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define DEFAULT_PORT 12346 #define MULTICAST_ADDRESS "239.0.0.1" int main() { int sockfd; struct sockaddr_in local_addr; struct ip_mreq multicast_req; char buffer[1024]; // 创建套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { perror("socket"); exit(1); } // 设置本地地址 memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = htonl(INADDR_ANY); local_addr.sin_port = htons(DEFAULT_PORT); // 绑定套接字到本地地址 if (bind(sockfd, (struct sockaddr *)&local_addr, sizeof(local_addr)) == -1) { perror("bind"); exit(1); } // 加入多播组 memset(&multicast_req, 0, sizeof(multicast_req)); multicast_req.imr_multiaddr.s_addr = inet_addr(MULTICAST_ADDRESS); multicast_req.imr_interface.s_addr = htonl(INADDR_ANY); if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&multicast_req, sizeof(multicast_req)) == -1) { perror("setsockopt"); exit(1); } printf("Waiting for multicast message...\n"); while (1) { // 接收多播消息 int recv_len = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, NULL, NULL); if (recv_len == -1) { perror("recvfrom"); exit(1); } buffer[recv_len] = '\0'; printf("Received multicast message: %s\n", buffer); } // 关闭套接字 close(sockfd); return 0; }
本文来自博客园,作者:Yasuo_Hasaki,转载请注明原文链接:https://www.cnblogs.com/hasaki-yasuo/p/18044461
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步