二、基于UDP的TFTP文件传输
1)tftp协议概述
简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输
特点:
是应用层协议
基于UDP协议实现
数据传输模式
octet:二进制模式(常用)
mail:已经不再支持
2)tftp下载模型
TFTP通信过程总结
- 服务器在69号端口等待客户端的请求
- 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
- 每个数据包的编号都有变化(从1开始)
- 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
- 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。
3)tftp协议分析
差错码:
0 未定义,差错错误信息
1 File not found.
2 Access violation.
3 Disk full or allocation exceeded.
4 illegal TFTP operation.
5 Unknown transfer ID.
6 File already exists.
7 No such user.
8 Unsupported option(s) requested.
#include <myhead.h>
#define SER_PORT 69 //服务器端口号
#define SER_IP "192.168.0.115" //服务器ip地址
#define CLI_PORT 5555 //客户端端口号
#define CLI_IP "192.168.0.100" //客户端ip地址
#define BUF_SIZE 516 //缓冲区大小
#define TIMEOUT 3 //超时时间(秒)
void display()
{
printf("请选择功能:\n");
printf("1. 文件下载\n");
printf("2. 文件上传\n");
printf("3. 退出程序\n");
}
int main(int argc, const char *argv[])
{
while (1)
{
int k = 0;
display();
int choice;
scanf("%d", &choice);
char filename[100];
if (choice == 1 || choice == 2)
{
printf("请输入文件名: ");
scanf("%s", filename);
}
int file = open(filename, O_RDWR | O_CREAT, 0664);
if (file == -1)
{
printf("无法打开文件 %s\n", filename);
return 1;
}
switch (choice)
{
case 1:
{
//1、创建用于通信的服务器套接字文件描述符
int cfd = socket(AF_INET, SOCK_DGRAM, 0);
if (cfd == -1)
{
perror("socket error");
return -1;
}
printf("cfd = %d\n", cfd); //3
//2、向服务器发送下载请求
char buf[BUF_SIZE];
short *p1 = (short *)buf;
*p1 = htons(1); //操作码
char *p2 = buf + 2;
strcpy(p2, filename); //文件名
char *p4 = p2 + strlen(p2) + 1;
strcpy(p4, "octet"); //模式位
int size = 2 + strlen(p2) + strlen(p4) + 2; //请求包的总长度
//将请求包发送给服务器
struct sockaddr_in sin;
socklen_t sinlen = sizeof(sin);
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(SER_IP);
sendto(cfd, buf, size, 0, (struct sockaddr *)&sin, sinlen);
while (1)
{
ssize_t str_len = recvfrom(cfd, buf, BUF_SIZE, 0, (struct sockaddr *)&sin, &sinlen);
if (ntohs(*p1) == 3)
{
write(file, buf + 4, str_len - 4);
*p1 = htons(4);
sendto(cfd, buf, size, 0, (struct sockaddr *)&sin, sinlen);
if (str_len < BUF_SIZE - 4)
{
break;
}
}
}
//发送分手请求
*p1 = htons(5);
sendto(cfd, buf, size, 0, (struct sockaddr *)&sin, sinlen);
close(cfd);
bzero(buf, BUF_SIZE);
break;
}
case 2:
{
// 1、创建用于通信的服务器套接字文件描述符
int cfd = socket(AF_INET, SOCK_DGRAM, 0);
if (cfd == -1)
{
perror("socket error");
return -1;
}
printf("cfd = %d\n", cfd); //3
// 2、向服务器发送上传请求
char buf[BUF_SIZE];
short *p1 = (short *)buf;
*p1 = htons(2); //操作码,表示上传
char *p2 = buf + 2;
strcpy(p2, filename); //文件名
char *p4 = p2 + strlen(p2) + 1;
strcpy(p4, "octet"); //模式位
int size = 2 + strlen(p2) + strlen(p4) + 2; //请求包的总长度
// 将请求包发送给服务器
struct sockaddr_in sin;
socklen_t sinlen = sizeof(sin);
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(SER_IP);
sendto(cfd, buf, size, 0, (struct sockaddr *)&sin, sinlen);
short *ack = (short *)(buf + 2);
int n = 1;
while (1)
{
ssize_t str_len = recvfrom(cfd, buf, BUF_SIZE, 0, (struct sockaddr *)&sin, &sinlen);
if (ntohs(*p1) == 4)
{
int res = read(file, buf + 4, BUF_SIZE - 4);
*p1 = htons(3);
*ack = htons(n);
n++;
sendto(cfd, buf, BUF_SIZE, 0, (struct sockaddr *)&sin, sinlen);
if (res < BUF_SIZE - 4)
{
break;
}
}
}
//发送分手请求
*p1 = htons(5);
sendto(cfd, buf, size, 0, (struct sockaddr *)&sin, sinlen);
close(cfd);
bzero(buf, BUF_SIZE);
break;
}
case 3:
{
k = 1;
break;
}
}
printf("k=%d\n", k);
if (k == 1)
{
break;
}
}
}