tftp套接字编程
实验要求
交互命令
- 从服务器下载文件
1
2
3tftp -i <服务器IP地址> GET <远程文件名> <本地保存文件名>
ex:
tftp -i 127.0.0.1 GET test.txt C:/user/30905/Desktop - 向服务器上传文件
1
2
3tftp -i <服务器IP地址> PUT <本地文件名> <远程保存文件名>
ex:
tftp -i 127.0.0.1 PUT test.txt testfile.txt - 下载文件的netascii模式
1
tftp <服务器IP地址> GET <远程文件名>
- 上传文件的netascii模式
1
tftp <服务器IP地址> PUT <本地文件名>
效果演示
- 正常状态下
- 文件下载
- 文件上传
- 文件下载
- 异常状态下
TFTP源码
注:仍有很多可以完善的地方,仅供参考
相信你可以做得更好!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// TFTP操作码定义(根据TFTP协议)
// TFTP错误码定义(根据TFTP协议)
// 定义TFTP传输模式
// 日志记录函数:记录系统行为日志到 "tftp_server.log"
void log_message(const char* message) {
FILE* log_file = fopen("tftp_server.log", "a"); // 以追加模式打开日志文件
if (log_file != NULL) {
time_t now = time(NULL); // 获取当前时间
fprintf(log_file, "%s: %s\n", ctime(&now), message); // 将时间和日志信息写入文件
fclose(log_file); // 关闭日志文件
}
}
// 发送错误包:根据TFTP协议构造并发送错误响应包
void send_error(SOCKET sock, struct sockaddr_in* client_addr, int addr_len, int error_code, const char* error_msg) {
char error_packet[BUFFER_SIZE];
*(unsigned short*)error_packet = htons(OP_ERROR); // 操作码(2字节)
*(unsigned short*)(error_packet + 2) = htons(error_code); // 错误码(2字节)
strcpy(error_packet + 4, error_msg); // 错误消息
sendto(sock, error_packet, strlen(error_msg) + 5, 0, (struct sockaddr*)client_addr, addr_len); // 发送错误包
}
// 处理客户端的请求,包括读请求和写请求
void handle_client(SOCKET sock, struct sockaddr_in* client_addr, int addr_len) {
// 处理客户端的请求,包括读请求和写请求
char buffer[BUFFER_SIZE]; // 用于接收和发送数据的缓冲区
int recv_len, bytes_transferred = 0; // 接收到的数据长度,已传输字节数
char* filename, * mode; // 文件名和传输模式
char log_buf[512]; // 日志缓冲区
// 从客户端接收数据
recv_len = recvfrom(sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)client_addr, &addr_len);
if (recv_len < 4) {
// 如果数据长度小于4字节,表示请求无效,发送错误包
send_error(sock, client_addr, addr_len, ERR_ILLEGAL_OP, "Illegal TFTP operation.");
return;
}
// 提取操作码和文件名、传输模式
unsigned short opcode = ntohs(*(unsigned short*)buffer); // 操作码(前两个字节)
filename = buffer + 2; // 文件名紧跟在操作码之后
mode = filename + strlen(filename) + 1; // 模式紧跟在文件名之后
// 检查传输模式是否为支持的模式(netascii或octet)
if (strcmp(mode, MODE_NETASCII) != 0 && strcmp(mode, MODE_OCTET) != 0) {
send_error(sock, client_addr, addr_len, ERR_ILLEGAL_OP, "Unsupported transfer mode.");
return;
}
// 根据操作码进行不同的处理
if (opcode == OP_RRQ) {
// 处理读请求(从服务器下载文件)
printf("Handling RRQ: Download %s in %s mode\n", filename, mode);
sprintf(log_buf, "Received RRQ for file: %s", filename); // 记录请求日志
log_message(log_buf);
// 打开要读取的文件
FILE* file = fopen(filename, mode[0] == 'o' ? "rb" : "r");
if (!file) {
// 如果文件不存在,发送文件未找到的错误包
send_error(sock, client_addr, addr_len, ERR_FILE_NOT_FOUND, "File not found.");
log_message("Error: File not found.");
printf("Error: File not found.");
return;
}
char data_packet[BUFFER_SIZE]; // 数据包缓冲区
unsigned short block_number = 1; // 初始化数据块编号
int bytes_read;
clock_t start_time = clock(); // 开始计时
// 循环逐块发送文件内容
do {
memset(data_packet, 0, BUFFER_SIZE); // 清空数据包缓冲区
*(unsigned short*)data_packet = htons(OP_DATA); // 操作码:数据包
*(unsigned short*)(data_packet + 2) = htons(block_number); // 数据块编号
bytes_read = fread(data_packet + 4, 1, DATA_SIZE, file); // 读取文件数据
int ack_received = 0; // 标记ACK是否已接收
int retries = 0; // 重传次数
while (!ack_received && retries < BACK_NUM) {
// 发送数据包
sendto(sock, data_packet, bytes_read + 4, 0, (struct sockaddr*)client_addr, addr_len);
printf("发送%hu号数据包", block_number);
// 设置超时重传(每次重传都重新设置超时)
struct timeval tv;
tv.tv_sec = TIMEOUT_SECONDS;
tv.tv_usec = 0;
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(sock, &read_fds);
// 等待ACK或超时
int select_result = select(sock + 1, &read_fds, NULL, NULL, &tv);
if (select_result > 0) {
// 有数据可读,接收ACK
recv_len = recvfrom(sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)client_addr, &addr_len);
if (recv_len >= 4 && ntohs(*(unsigned short*)buffer) == OP_ACK && ntohs(*(unsigned short*)(buffer + 2)) == block_number) {
printf(",已收到%hu号数据包\n", block_number);
ack_received = 1; // 收到ACK,退出重传循环
}
else {
printf(",收到无效ACK,重新发送%hu号数据包\n", block_number);
retries++;
}
}
else if (select_result == 0) {
// 超时,重传数据包
printf("超时,重新发送%hu号数据包\n", block_number);
retries++;
}
else {
// select出错
perror("select() error");
fclose(file);
return;
}
}
bytes_transferred += bytes_read; // 累加已传输的字节数
block_number++; // 发送下一块数据
} while (bytes_read == DATA_SIZE); // 如果数据块大小不足512字节,表示文件已传输完毕
fclose(file); // 关闭文件
// 计算并显示传输吞吐量
double time_elapsed = (double)(clock() - start_time) / CLOCKS_PER_SEC;
printf("文件下载成功,已传输字节数:%d,传输时间:%.4lf s,下载吞吐量:%.2f Bps\n", bytes_transferred, time_elapsed, bytes_transferred / time_elapsed);
sprintf(log_buf, "Transfer completed for file: %s, Throughput: %.2f Bps", filename, bytes_transferred / time_elapsed);
log_message(log_buf);
}
else if (opcode == OP_WRQ) {
// 处理写请求(上传文件到服务器)
printf("Handling WRQ: Upload %s in %s mode\n", filename, mode);
sprintf(log_buf, "Received WRQ for file: %s", filename);
log_message(log_buf);
// 创建要写入的文件
FILE* file = fopen(filename, mode[0] == 'o' ? "wb" : "w");
if (!file) {
// 如果文件无法打开,发送访问错误
send_error(sock, client_addr, addr_len, ERR_ACCESS_VIOLATION, "Access violation.");
log_message("Error: Access violation.");
printf("文件无法打开\n");
return;
}
unsigned short block_number = 0; // 初始化ACK块编号
clock_t start_time = clock(); // 开始计时
// 循环接收客户端上传的数据
do {
// 发送ACK确认包,ACK包中包含上一个接收的块编号
memset(buffer, 0, BUFFER_SIZE);
*(unsigned short*)buffer = htons(OP_ACK); // 操作码:确认包
*(unsigned short*)(buffer + 2) = htons(block_number); // 确认的块编号
sendto(sock, buffer, 4, 0, (struct sockaddr*)client_addr, addr_len);
// 接收数据包
recv_len = recvfrom(sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)client_addr, &addr_len);
if (recv_len < 4 || ntohs(*(unsigned short*)buffer) != OP_DATA) {
// 如果收到的不是数据包,发送错误
send_error(sock, client_addr, addr_len, ERR_ILLEGAL_OP, "Illegal TFTP operation.");
fclose(file);
return;
}
printf("已收到%hu号数据包\n", block_number);
block_number++; // 块编号递增
bytes_transferred += recv_len - 4; // 累加已接收的字节数
fwrite(buffer + 4, 1, recv_len - 4, file); // 将数据写入文件
} while (recv_len == DATA_SIZE + 4); // 如果接收到的数据块小于512字节,则传输结束
memset(buffer, 0, BUFFER_SIZE);
*(unsigned short*)buffer = htons(OP_ACK); // 操作码:确认包
*(unsigned short*)(buffer + 2) = htons(block_number); // 确认的块编号
sendto(sock, buffer, 4, 0, (struct sockaddr*)client_addr, addr_len);
fclose(file); // 关闭文件
// 计算并显示上传吞吐量
double time_elapsed = (double)(clock() - start_time) / CLOCKS_PER_SEC;
printf("文件上传成功,已传输字节:%d,传输时间:%.4lf s,上传吞吐量:%.2f Bps\n", bytes_transferred, time_elapsed, bytes_transferred / time_elapsed);
sprintf(log_buf, "Upload completed for file: %s, Throughput: %.2f Bps", filename, bytes_transferred / time_elapsed);
log_message(log_buf);
}
else {
// 对于无效的操作码,发送非法操作的错误包
send_error(sock, client_addr, addr_len, ERR_ILLEGAL_OP, "Illegal TFTP operation.");
log_message("Error: Illegal TFTP operation.");
}
}
// 主函数,负责初始化服务器并处理客户端请求
int main() {
WSADATA wsa;
SOCKET sock;
struct sockaddr_in server_addr, client_addr;
int addr_len = sizeof(client_addr);
printf("正在初始化tftp服务器...\n");
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
printf("Winsock initialization failed. Error Code: %d\n", WSAGetLastError());
return 1;
}
printf("Winsock initialized.\n");
// 创建UDP套接字
if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
printf("Could not create socket: %d\n", WSAGetLastError());
return 1;
}
printf("Socket created.\n");
// 配置服务器地址结构
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口
server_addr.sin_port = htons(PORT); // 绑定到TFTP默认端口69
// 绑定套接字
if (bind(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
printf("Bind failed with error code: %d\n", WSAGetLastError());
return 1;
}
printf("Bind successful. Waiting for clients...\n");
// 服务器主循环,等待并处理客户端请求
while (1) {
// 每次接收到客户端请求时,处理该请求
handle_client(sock, &client_addr, addr_len);
}
// 关闭套接字并清理Winsock
closesocket(sock);
WSACleanup();
return 0;
}