- 编写客户端,使用TFTP协议,完成文件下载的功能。
- 1.安装tftpd32
- 2.TFTP协议
- 3.打开服务器tftpd32
- 4.客户端
- 代码实现
- 执行结果
- 注意
- 6.非原创
- 基于C语言实现,TFTP练习——用recvfrom( )/ sendto( )
- 命令行输入指定IP、端口
2.TFTP协议教程链接
- 数据传输模式:
octet:二进制模式
- TFTP通信过程
- TFTP通信过程
1、服务器在69号端口等待客户端的请求
2、服务器若批准此请求,则使用临时端口与客户端进行通信
3、每个数据包的编号都有变化(从1开始)
4、每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的包(数据或ACK)
5、数据的长度以512Byte传输
6、小于512Byte的数据意味着传输结束
- TFTP协议分析
//-------编写客户端, 使用TFTP协议,完成文件下载的功能---------- #include执行结果#include #include #include #include #include #include #include #include #include #include #define ERRLOG(errmsg) do { printf("%s--%s(%d):", __FILE__, __func__, __LINE__); perror(errmsg); exit(-1); } while (0) int main(int argc, char const *argv[]) { //检测命令行3个参数 if (3 != argc) { printf("Usage : %s n", argv[0]); exit(-1); } // 1.创建套接字 // socket返回的文件描述符 //IPV4 //UDP int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (-1 == sockfd) ERRLOG("socket error"); // 2.填充服务器网络信息结构体 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); //清空 server_addr.sin_family = AF_INET; // IPV4 LOOP://注意 goto的入口,因为即使是出错,erver_addr中的 69 也被临时端口覆盖了 //端口号 填69 //将无符号2字节整型 主机-->网络//atoi输入字符串转换为一个整数 server_addr.sin_port = htons(atoi(argv[2])); // windows 的ip地址 //将所指的字符串转换成32位的网络字节序二进制值 server_addr.sin_addr.s_addr = inet_addr(argv[1]); //结构体长度 socklen_t server_addr_len = sizeof(server_addr); //----------------------------------------------- //数据包--数据的长度以512Byte传输 unsigned char buff[600] = {0}; //返回的ACK unsigned char _ack[4]; unsigned short code = 0; //操作码 unsigned short num = 0; //块编号 或者 错误码 //数据的长度以512Byte传输 char text[512] = {0}; //文件内容 或 错误信息 //校验收到的块编号 int N = 0; //返回的文件描述符 int fd; int ret = 0; //输入文件名 char filename[32] = {0}; printf("下载文件名: "); scanf("%s", filename); //使用 sprintf 组包 //返回值:成功格式化字符的个数 //-读- //文件名 //0 //二进制模式//0 ret = sprintf(buff, "%c%c%s%c%s%c", 0, 1, filename, 0, "octet", 0); //首次发送请求-----想要发送的数据的字节数-阻塞 if (-1 == sendto(sockfd, buff, ret, 0, (struct sockaddr *)&server_addr, server_addr_len)) ERRLOG("recvfrom error"); //循环接收服务器发来的数据包 while (1) { //接收--需要保存服务器的网络信息结构体 因为里面有临时端口 if (-1 == (ret = recvfrom(sockfd, buff, 600, 0, (struct sockaddr *)&server_addr, &server_addr_len))) ERRLOG("recvfrom error"); //解析数据包中的内容 //将无符号2字节整型 网络-->主机 //解析操作码 code = ntohs(*(unsigned short *)buff); //解析块编号 或者 错误码 num = ntohs(*(unsigned short *)(buff + 2)); //解析文件内容 或 错误信息 strncpy(text, buff + 4, 512); if (3 == code && num == N + 1) { //校验块编号+1 N++; //要接收的数据包 //如果是第一次接到数据包 要创建文件 if (num == 1) { if (-1 == (fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664))) ERRLOG("open error"); } //将文件内容写入文件 if (-1 == write(fd, text, ret - 4)) ERRLOG("write error"); // 组装ACK *(unsigned short *)_ack = htons(4); *(unsigned short *)(_ack + 2) = htons(num); 回复ACK包 if (-1 == sendto(sockfd, _ack, 4, 0, (struct sockaddr *)&server_addr, server_addr_len)) ERRLOG("recvfrom error"); //文件接收完毕 if (ret < 512) break; } else if (5 == code) { printf("接收出错[%s]n", text); //错误信息 goto LOOP; } } printf("文件[%s]下载完成n", filename); close(sockfd); return 0; }
tftpd32目录下
未知原因报警告
6.非原创文件重命名解决



