1.基于UDP的TFTP文件传输
tftp协议概述
简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输
特点:
是应用层协议
基于UDP协议实现
数据传输模式
octet:二进制模式(常用)
mail:已经不再支持
代码:
#include <myhead.h>
#define TFTP_PORT 69
#define BLOCK_SIZE 512
#define OPCODE_RRQ 1
#define OPCODE_WRQ 2
#define OPCODE_DATA 3
#define OPCODE_ACK 4
#define OPCODE_ERROR 5
#define MAX_RETRIES 5
void error(const char *msg)
{
perror(msg);
exit(1);
}
void send_rrq(const char *filename, const char *server_ip)
{
int sockfd;
struct sockaddr_in server_addr;
unsigned char buffer[516]; // 512 + 4 比特
int n;
unsigned int block_number = 1;
// 创建UDP套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
error("Socket creation failed");
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(TFTP_PORT);
inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
// 发送读取请求
buffer[0] = 0; // 高字节
buffer[1] = OPCODE_RRQ; // 操作码
// 复制文件名和模式到缓冲区
size_t filename_len = strlen(filename);
size_t mode_len = strlen("octet");
if (filename_len + mode_len + 4 > sizeof(buffer))
{
fprintf(stderr, "Filename and mode too long\n");
close(sockfd);
return;
}
memcpy(buffer + 2, filename, filename_len);
buffer[2 + filename_len] = 0; // 文件名结束
memcpy(buffer + 3 + filename_len, "octet", mode_len);
buffer[3 + filename_len + mode_len] = 0; // 模式结束
// 发送请求
if (sendto(sockfd, buffer, 4 + filename_len + mode_len, 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
error("Send RRQ failed");
}
// 接收数据
while (1)
{
socklen_t addr_len = sizeof(server_addr);
n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&server_addr, &addr_len);
if (n < 0)
{
error("Receive failed");
}
// 处理数据包
unsigned short opcode = (buffer[0] << 8) | buffer[1];
if (opcode == OPCODE_DATA)
{
unsigned short received_block_number = (buffer[2] << 8) | buffer[3];
if (received_block_number == block_number)
{
// 写入文件
FILE *file = fopen(filename, "ab");
if (file == NULL)
{
error("File open failed");
}
fwrite(buffer + 4, 1, n - 4, file);
fclose(file);
// 发送ACK
buffer[0] = 0; // 高字节
buffer[1] = OPCODE_ACK; // 操作码
buffer[2] = (block_number >> 8) & 0xFF; // 发送ACK的块号
buffer[3] = block_number & 0xFF;
if (sendto(sockfd, buffer, 4, 0, (struct sockaddr *)&server_addr, addr_len) < 0)
{
error("Send ACK failed");
}
// 如果接收到的数据块小于512字节,表示传输结束
if (n < 4 + BLOCK_SIZE) {
printf("File transfer complete.\n");
break;
}
block_number++;
}
} else if (opcode == OPCODE_ERROR)
{
printf("下载错误: %s\n", buffer + 4);
break;
}
}
close(sockfd);
}
void send_wrq(const char *filename, const char *server_ip)
{
int sockfd;
struct sockaddr_in server_addr;
unsigned char buffer[516]; // 512 + 4 比特
int n;
unsigned int block_number = 0;
// 创建UDP套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
error("Socket creation failed");
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(TFTP_PORT);
inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
// 发送写入请求
buffer[0] = 0; // 高字节
buffer[1] = OPCODE_WRQ; // 操作码
// 复制文件名和模式到缓冲区
size_t filename_len = strlen(filename);
size_t mode_len = strlen("octet");
if (filename_len + mode_len + 4 > sizeof(buffer))
{
fprintf(stderr, "Filename and mode too long\n");
close(sockfd);
return;
}
memcpy(buffer + 2, filename, filename_len);
buffer[2 + filename_len] = 0; // 文件名结束
memcpy(buffer + 3 + filename_len, "octet", mode_len);
buffer[3 + filename_len + mode_len] = 0; // 模式结束
// 发送请求
if (sendto(sockfd, buffer, 4 + filename_len + mode_len, 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
error("Send WRQ failed");
}
// 等待ACK
socklen_t addr_len = sizeof(server_addr);
n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&server_addr, &addr_len);
if (n < 0)
{
error("Receive failed");
}
unsigned short opcode = (buffer[0] << 8) | buffer[1];
if (opcode != OPCODE_ACK)
{
printf("等待ACK错误: %s\n", buffer + 4);
close(sockfd);
return;
}
// 读取文件并发送数据
FILE *file = fopen(filename, "rb");
if (file == NULL)
{
error("File open failed");
}
while (1)
{
// 读取数据块
n = fread(buffer + 4, 1, BLOCK_SIZE, file);
if (n < 0)
{
error("File read failed");
}
if (n == 0)
{
// 如果读取到文件末尾,则结束传输
printf("上传完成.\n");
break;
}
block_number++;
buffer[0] = 0; // 高字节
buffer[1] = OPCODE_DATA; // 操作码
buffer[2] = (block_number >> 8) & 0xFF; // 块号
buffer[3] = block_number & 0xFF; // 块号
int retries = 0;
while (retries < MAX_RETRIES)
{
// 发送数据块
printf("Sending block %d, size: %d bytes\n", block_number, n);
if (sendto(sockfd, buffer, n + 4, 0, (struct sockaddr *)&server_addr, addr_len) < 0)
{
error("Send DATA failed");
}
// 等待ACK
n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&server_addr, &addr_len);
if (n < 0)
{
printf("Receive failed, retrying...\n");
retries++;
continue; // 继续重试
}
opcode = (buffer[0] << 8) | buffer[1];
if (opcode == OPCODE_ACK)
{
printf("Received ACK for block %d\n", block_number);
break; // 收到ACK,退出重试
} else
{
printf("Error received from server: %s\n", buffer + 4);
break;
}
}
if (retries == MAX_RETRIES)
{
printf("Failed to receive ACK after %d retries. Aborting upload.\n", MAX_RETRIES);
break;
}
}
fclose(file);
close(sockfd);
}
int main()
{
char server_ip[16];
char filename[256];
int choice;
printf("请输入TFTP服务器IP地址: ");
scanf("%s", server_ip);
while (1) {
printf("选择操作:\n1. 下载文件 (RRQ)\t2. 上传文件 (WRQ)\t3. 退出\n");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("请输入要下载的文件名: ");
scanf("%s", filename);
send_rrq(filename, server_ip);
break;
case 2:
printf("请输入要上传的文件名: ");
scanf("%s", filename);
send_wrq(filename, server_ip);
break;
case 3:
printf("退出成功。\n");
exit(0);
default:
printf("无效的选择。\n");
break;
}
}
return 0;
}
运行结果:
2.思维导图