目录
- 编写客户端,使用TFTP协议,完成文件下载的功能。
- 1.安装tftpd32
- 2.TFTP协议
- 3.打开服务器tftpd32
- 4.客户端
- 代码实现
- 执行结果
- 注意
- 6.非原创
编写客户端,使用TFTP协议,完成文件下载的功能。
- 基于C语言实现,TFTP练习——用recvfrom( )/ sendto( )
- 命令行输入指定IP、端口
1.安装tftpd32
教程链接
2.TFTP协议
- 数据传输模式:
octet:二进制模式
- TFTP通信过程
- TFTP通信过程
1、服务器在69号端口等待客户端的请求
2、服务器若批准此请求,则使用临时端口与客户端进行通信
3、每个数据包的编号都有变化(从1开始)
4、每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的包(数据或ACK)
5、数据的长度以512Byte传输
6、小于512Byte的数据意味着传输结束
- TFTP协议分析
3.打开服务器tftpd32
4.客户端
代码实现
//-------编写客户端, 使用TFTP协议,完成文件下载的功能----------
#include <stdio.h>
/*exit*/
#include <stdlib.h>
/*socket*/
#include <sys/types.h>
#include <sys/socket.h>
/*sockaddr_in结构体*/
#include <netinet/in.h>
#include <netinet/ip.h>
/*manset*/
#include <string.h>
/*socket*/
#include <arpa/inet.h>
/*write-close*/
#include <unistd.h>
/*open*/
#include <sys/stat.h>
#include <fcntl.h>
#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 <IP> <PORT>\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.非原创
更多推荐
Linux&C语言简单实现客户端使用TFTP协议文件下载-网络编程-应用层
发布评论