这段代码实现了一个简单的 TFTP(Trivial File Transfer Protocol)客户端程序,用于从服务器下载文件。它创建一个 UDP 套接字,向指定的 TFTP 服务器发送文件下载请求,并接收服务器传来的数据块,直至文件完整下载到本地。如果遇到错误,如文件不存在或服务器响应错误,则会相应处理并输出错误信息。
TFTP是一个基于UDP的应用层协议,它主要用于简单的文件传输需求,由于是基于UDP协议的,所以这个数据传输并不可靠,偶尔会出现丢包现象,属实正常。
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
// tftp下载文件
int main(int argc, char const *argv[])
{
if (argc < 2)
{
perror("参数有误,例如(./a.out 文件名)\n");
exit(-1);
}
// 创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // AF_INET:ipv4协议族 SOCK_DGRAM:类型套接字
if (sockfd < 0)
{
perror("套接字创建失败\n");
exit(-1);
}
// 组装下载请求的报文--
// --读写请求所占格式
// 操作码2字节(%c%c) 文件名(%s) 0(%c) 模式(%s) 0(%C) 选线1(%s) 0 值1(%s) 0.....选项n(%s) 0 值n(%s) 0
// "octet"二进制模式传输
char buf[1024] = "";
int len = sprintf(buf, "%c%c%s%c%s%c", 0, 1, argv[1], 0, "octet", 0); // 返回组装的长度
// 创建tftp服务器地址信息
struct sockaddr_in serv_addr;
bzero(&serv_addr, sizeof(serv_addr)); // 将地址信息清空
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(69); // 将主机字节序转换为网络字节序
inet_pton(AF_INET, "192.168.10.125", &serv_addr.sin_addr);
socklen_t serv_addr_len = sizeof(serv_addr);
// 发送请求报文
ssize_t send_len = sendto(sockfd, buf, len, 0, (struct sockaddr *)&serv_addr, serv_addr_len);
if (send_len < 0)
{
perror("发送请求报文失败\n");
exit(-1);
}
// 创建文件并打开文件
int fd = open(argv[1], O_CREAT | O_RDWR, 0666);
if (fd < 0)
{
perror("创建文件失败\n");
exit(-1);
}
/*循环接收数据包
接收到数据包后,先获取数据包中的操作码
*/
while (1)
{
char msg[516] = ""; // 最大数据包为516
// 接收服务器的数据
ssize_t recv_len = recvfrom(sockfd, msg, sizeof(msg), 0, (struct sockaddr *)&serv_addr, &serv_addr_len);
if (recv_len < 0)
{
perror("接收数据失败\n");
exit(-1);
}
// 获取报文中操作码 操作码占前两个字节
unsigned short op = ntohs(*((unsigned short *)&msg));
if (op == 3)
{
unsigned short num = ntohs(*(unsigned short *)(msg + 2));
// printf("块编号:%hu\n", num);
// 获取数据,将数据写到本地文件中
write(fd, msg + 4, recv_len - 4);
// 给服务器ack确认报文
msg[1] = 4;
sendto(sockfd, msg, 4, 0, (struct sockaddr *)&serv_addr, serv_addr_len);
// 当数据传输完时,跳出循环,结束
if (recv_len < 516)
{
break;
}
}
else if (op == 5)
{
printf("出错,错误码=%hu,错误信息=%s\n", ntohs(*(unsigned short *)msg + 2), msg + 4);
}
}
// 关闭套接字
close(fd);
close(sockfd);
return 0;
}
数据包中的操作码
1.未定义(Undefined) - 这个错误代码没有明确的定义,可能是由于未知的原因导致的错误。
2.找不到文件(File not found) - 当服务器无法找到客户端请求的文件时返回此错误。
3.访问冲突(Access violation) - 当客户端试图访问服务器上的文件而没有足够的权限时发生。
4.磁盘已满或超过分配(Disk full or allocation exceeded) - 当服务器磁盘空间不足或超出分配限制时返回此错误。
5.非法的TFTP操作(Illegal TFTP operation) - 当客户端发送了一个服务器不支持或无效的操作码时返回此错误。
6.未知的传输ID(Unknown transfer ID) - 当服务器无法识别客户端请求的传输标识符时返回此错误。
7.文件已经存在(File already exists) - 当客户端试图上传一个已经在服务器上存在的文件时返回此错误。
8.没有这样的用户(No such user) - 当客户端请求的用户名不存在时返回此错误。请注意,尽管TFTP协议通常不需要身份验证,但在某些情况下可能会检查用户名。
9.不支持的选项请求(Unsupported option requested) - 当客户端请求服务器不支持的选项时返回此错误。