tftp 源代码解析

我先研究udp 传输的机制,tftp是用udp 设计的一个不错应用。

在网上查找tftp 源代码,发现 https://github.com/ideawu/tftpx 上的源码比较好看,这个只是linux 下的代码。

在ubuntu 上make 了一下,就编译好了,然后测试程序,能按tftp 的方式运行。

那个链接包含服务端和客户端代码,我这里只是分析其客户端代码,因为就算客户端代码也很大的,相比其他的代码,这个还是算简单的。

这个客户端代码包含3个文件,

tftpx.h 定义tftp中的常量,与服务端程序共享。

client.h 就是几个函数的全局定义。

client.c 这个是主程序。

在查看 tftpx 的源码之前, 你最好先阅读 W.Richard.Stevens 的 TCP/IP Illustrated Volume 1: The Protocols(TCP/IP详解 卷1:协议).

tftpx 使用这样的代码来实现停止等待机制:

int send_packet(int sock, struct tftpx_packet *packet, int size){
	struct tftpx_packet rcv_packet;
	int time_wait_ack = 0;
	int rxmt = 0;
	int r_size = 0;

	for(rxmt = 0; rxmt < PKT_MAX_RXMT; rxmt ++){
		printf("Send block=%d\n", ntohs(packet->block));
		if(send(sock, packet, size, 0) != size){
			return -1;
		}
		for(time_wait_ack = 0; time_wait_ack < PKT_RCV_TIMEOUT; time_wait_ack += 20000){
			usleep(20000);
			// Try receive(Nonblock receive).
			r_size = recv(sock, &rcv_packet, sizeof(struct tftpx_packet), MSG_DONTWAIT);
			if(r_size >= 4 && rcv_packet.cmd == htons(CMD_ACK) && rcv_packet.block == packet->block){
				//printf("ACK: block=%d\n", ntohs(rcv_packet.block));
				// Valid ACK
				break;
			}
		}
		if(time_wait_ack < PKT_RCV_TIMEOUT){
			break;
		}else{
			// Retransmission.
			continue;
		}
	}
	if(rxmt == PKT_MAX_RXMT){
		// send timeout
		printf("Sent packet exceeded PKT_MAX_RXMT.\n");
		return -1;
	}

	return size;
}

tftp 的速度问题 

从这个停止等待机制看,tftp 速度应该有点慢。

网上搜索tftp 速度,结果都说慢,都怀疑网络问题,从这个机制我们可以评估速度。因为usleep(20000)需要20ms 才能传一个包,一个包大小一般512字节,理想情况下 512x50=25600 字节/秒  50=1000/20

所以传送速度就是25k 字节/秒

这个usleep(20000) 可能有点不得已,每个包都要核实好了,才能下一个包,可能比ftp 都会慢。

要提高速度可以加大包的尺寸,减少usleep的数据。

或者采用多个包才核实等传送机制,如果就tftp 可能没办法。

主要的几个函数

void do_list(int sock, char *dir);  实现目录列表
void do_get(char *remote_file, char *local_file);  从服务端获取文件的实现
void do_put(char *filename);  发送文件到服务端

int main(int argc, char **argv) 这个是主函数

命令行分析,必须输入服务端的ip 地址, 端口号是可选的,如果与他的服务程序通讯,就是缺省为:

#define SERVER_PORT 10220

在建立通讯sock前,显示应用帮助文件

建立一个sock ,没有通讯测试一样

然后等待你输入tftp 命令,解析命令,然后调用上面3个函数,完成功能。

支持的tftp命令是 list,get,put,blocksize,quit

可以看帮助文件的函数实现:

void help(){
	printf("Usage: cmd  arg0[,arg1,arg2...]\n");
	printf("  -Directory listing:\n");
	printf("    list path\n");
	printf("  -Download a file from the server:\n");
	printf("    get remote_file[ local_file]\n");
	printf("  -Upload a file to the server:\n");
	printf("    put filename\n");
	printf("  -Set blocksize:\n");
	printf("    blocksize size\n");
	printf("  -Quit this programm:\n");
	printf("    quit\n");
}

client.c 主程序

/**********************************************
 * Author: ideawu(www.ideawu.net)
 * Date: 2007-06
 * File: client.c
 *********************************************/

#include "client.h"

// Socket fd this client use.
int sock;
// Server address.
struct sockaddr_in server;
socklen_t addr_len;
int blocksize = DATA_SIZE;

void help(){
	printf("Usage: cmd  arg0[,arg1,arg2...]\n");
	printf("  -Directory listing:\n");
	printf("    list path\n");
	printf("  -Download a file from the server:\n");
	printf("    get remote_file[ local_file]\n");
	printf("  -Upload a file to the server:\n");
	printf("    put filename\n");
	printf("  -Set blocksize:\n");
	printf("    blocksize size\n");
	printf("  -Quit this programm:\n");
	printf("    quit\n");
}

int main(int argc, char **argv){
	char cmd_line[LINE_BUF_SIZE];
	char *buf;
	char *arg;
	int i;
	char *local_file;
	
	int done = 0;	// Server exit.
	char *server_ip;
	unsigned short port = SERVER_PORT;

	addr_len = sizeof(struct sockaddr_in);	
	
	if(argc < 2){
		printf("Usage: %s server_ip [server_port]\n", argv[0]);
		printf("    server_port - default 10220\n");
		return 0;
	}
	help();
	
	server_ip = argv[1];
	if(argc > 2){
		port = (unsigned short)atoi(argv[2]);
	}
	printf("Connect to server at %s:%d", server_ip, port);
	
	if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0){
		printf("Server socket could not be created.\n");
		return 0;
	}
	
	// Initialize server address
	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	inet_pton(AF_INET, server_ip, &(server.sin_addr.s_addr));
	
	// Command line interface.
	while(1){
		printf(">> ");
		memset(cmd_line, 0, LINE_BUF_SIZE);
		buf = fgets(cmd_line, LINE_BUF_SIZE, stdin);
		if(buf == NULL){
			printf("\nBye.\n");
			return 0;
		}
		
		arg = strtok (buf, " \t\n");
		if(arg == NULL){
			continue;
		}
		
		if(strcmp(arg, "list") == 0){
			arg = strtok (NULL, " \t\n");
			if(arg == NULL){
				printf("Error: missing arguments\n");
			}else{
				do_list(sock, arg);
			}
		}else if(strcmp(arg, "get") == 0){
			arg = strtok (NULL, " \t\n");
			local_file = strtok (NULL, " \t\n");
			if(arg == NULL){
				printf("Error: missing arguments\n");
			}else{
				if(local_file == NULL){
					local_file = arg;
				}
				do_get(arg, local_file);
			}
		}else if(strcmp(arg, "put") == 0){
			arg = strtok (NULL, " \t\n");
			if(arg == NULL){
				printf("Error: missing arguments\n");
			}else{
				do_put(arg);
			}
		}else if(strcmp(arg, "blocksize") == 0){
			arg = strtok (NULL, " \t\n");
			if(arg == NULL){
				printf("Error: missing arguments\n");
			}else{
				int blk = atoi(arg);
				if(blk > 0 && blk <= DATA_SIZE){
					blocksize = blk;
				}else{
					printf("Error: blocksize should be > 0 && <= DATA_SIZE\n");
				}
			}
		}else if(strcmp(arg, "quit") == 0){
			break;
		}else{
			printf("Unknow command.\n");
		}
		
	}
	return 0;
}

// Download a file from the server.
void do_get(char *remote_file, char *local_file){
	struct tftpx_packet snd_packet, rcv_packet;
	int next_block = 1;
	int recv_n;
	int total_bytes = 0;
	struct tftpx_packet ack;	
	struct sockaddr_in sender;
		
	int r_size = 0;
	int time_wait_data;
	ushort block = 1;
	
	// Send request.
	snd_packet.cmd = htons(CMD_RRQ);
	sprintf(snd_packet.filename, "%s%c%s%c%d%c", remote_file, 0, "octet", 0, blocksize, 0);
	sendto(sock, &snd_packet, sizeof(struct tftpx_packet), 0, (struct sockaddr*)&server, addr_len);
	
	FILE *fp = fopen(local_file, "w");
	if(fp == NULL){
		printf("Create file \"%s\" error.\n", local_file);
		return;
	}
	
	// Receive data.
	snd_packet.cmd = htons(CMD_ACK);
	do{
		for(time_wait_data = 0; time_wait_data < PKT_RCV_TIMEOUT * PKT_MAX_RXMT; time_wait_data += 10000){
			// Try receive(Nonblock receive).
			r_size = recvfrom(sock, &rcv_packet, sizeof(struct tftpx_packet), MSG_DONTWAIT,
					(struct sockaddr *)&sender,
					&addr_len);
			if(r_size > 0 && r_size < 4){
				printf("Bad packet: r_size=%d\n", r_size);
			}
			if(r_size >= 4 && rcv_packet.cmd == htons(CMD_DATA) && rcv_packet.block == htons(block)){
				printf("DATA: block=%d, data_size=%d\n", ntohs(rcv_packet.block), r_size - 4);
				// Send ACK.
				snd_packet.block = rcv_packet.block;
				sendto(sock, &snd_packet, sizeof(struct tftpx_packet), 0, (struct sockaddr*)&sender, addr_len);
				fwrite(rcv_packet.data, 1, r_size - 4, fp);
				break;
			}
			usleep(10000);
		}
		if(time_wait_data >= PKT_RCV_TIMEOUT * PKT_MAX_RXMT){
			printf("Wait for DATA #%d timeout.\n", block);
			goto do_get_error;
		}
		block ++;
	}while(r_size == blocksize + 4);
	//printf("\nReceived %d bytes.\n", total_bytes);
	
do_get_error:
	fclose(fp);
}


// Upload a file to the server.
void do_put(char *filename){
	struct sockaddr_in sender;
	struct tftpx_packet rcv_packet, snd_packet;
	int r_size = 0;
	int time_wait_ack;
	
	// Send request and wait for ACK#0.
	snd_packet.cmd = htons(CMD_WRQ);
	sprintf(snd_packet.filename, "%s%c%s%c%d%c", filename, 0, "octet", 0, blocksize, 0);	
	sendto(sock, &snd_packet, sizeof(struct tftpx_packet), 0, (struct sockaddr*)&server, addr_len);	
	for(time_wait_ack = 0; time_wait_ack < PKT_RCV_TIMEOUT; time_wait_ack += 20000){
		// Try receive(Nonblock receive).
		r_size = recvfrom(sock, &rcv_packet, sizeof(struct tftpx_packet), MSG_DONTWAIT,
				(struct sockaddr *)&sender,
				&addr_len);
		if(r_size > 0 && r_size < 4){
			printf("Bad packet: r_size=%d\n", r_size);
		}
		if(r_size >= 4 && rcv_packet.cmd == htons(CMD_ACK) && rcv_packet.block == htons(0)){
			break;
		}
		usleep(20000);
	}
	if(time_wait_ack >= PKT_RCV_TIMEOUT){
		printf("Could not receive from server.\n");
		return;
	}
	
	FILE *fp = fopen(filename, "r");
	if(fp == NULL){
		printf("File not exists!\n");
		return;
	}
	
	int s_size = 0;
	int rxmt;
	ushort block = 1;
	snd_packet.cmd = htons(CMD_DATA);
	// Send data.
	do{
		memset(snd_packet.data, 0, sizeof(snd_packet.data));
		snd_packet.block = htons(block);
		s_size = fread(snd_packet.data, 1, blocksize, fp);
		
		for(rxmt = 0; rxmt < PKT_MAX_RXMT; rxmt ++){
			sendto(sock, &snd_packet, s_size + 4, 0, (struct sockaddr*)&sender, addr_len);
			printf("Send %d\n", block);
			// Wait for ACK.
			for(time_wait_ack = 0; time_wait_ack < PKT_RCV_TIMEOUT; time_wait_ack += 20000){
				// Try receive(Nonblock receive).
				r_size = recvfrom(sock, &rcv_packet, sizeof(struct tftpx_packet), MSG_DONTWAIT,
						(struct sockaddr *)&sender,
						&addr_len);
				if(r_size > 0 && r_size < 4){
					printf("Bad packet: r_size=%d\n", r_size);
				}
				if(r_size >= 4 && rcv_packet.cmd == htons(CMD_ACK) && rcv_packet.block == htons(block)){
					break;
				}
				usleep(20000);
			}
			if(time_wait_ack < PKT_RCV_TIMEOUT){
				// Send success.
				break;
			}else{
				// Retransmission.
				continue;
			}
		}
		if(rxmt >= PKT_MAX_RXMT){
			printf("Could not receive from server.\n");
			return;
		}
		
		block ++;
	}while(s_size == blocksize);
	
	printf("\nSend file end.\n");
	
do_put_error:
	fclose(fp);
	
	return;
}


// Directory listing.
void do_list(int sock, char *dir){
	struct tftpx_packet packet;	
	int next_block = 1;
	int recv_n;
	struct tftpx_packet ack;	
	struct sockaddr_in sender;
	
	ack.cmd = htons(CMD_ACK);
	
	int r_size = 0;
	int time_wait_data;
	ushort block = 1;
	
	// Send request.
	packet.cmd = htons(CMD_LIST);
	strcpy(packet.data, dir);
	sendto(sock, &packet, sizeof(struct tftpx_packet), 0, (struct sockaddr*)&server, addr_len);
	
	printf("type\tsize\tname\n");
	printf("-------------------------------------------------\n");
	
	// Receive data.
	do{
		for(time_wait_data = 0; time_wait_data < PKT_RCV_TIMEOUT * PKT_MAX_RXMT; time_wait_data += 20000){
			// Try receive(Nonblock receive).
			r_size = recvfrom(sock, &packet, sizeof(packet), MSG_DONTWAIT,
					(struct sockaddr *)&sender,
					&addr_len);
			if(r_size > 0 && r_size < 4){
				printf("Bad packet: r_size=%d\n", r_size);
			}
			if(r_size >= 4 && packet.cmd == htons(CMD_DATA) && packet.block == htons(block)){
				block ++;
				ack.block = packet.block;
				sendto(sock, &ack, sizeof(struct tftpx_packet), 0, (struct sockaddr*)&sender, addr_len);
				fwrite(packet.data, 1, r_size - 4, stdout);
				break;
			}
			usleep(20000);
		}
		if(time_wait_data >= PKT_RCV_TIMEOUT * PKT_MAX_RXMT){
			printf("Wait for DATA #%d timeout.\n", block);
			return;
		}
	}while(r_size == blocksize + 4);
}

client.h 包含文件

/**********************************************
 * Author: ideawu(www.ideawu.net)
 * Date: 2007-06
 * File: client.h
 *********************************************/

#include "tftpx.h"

#define LINE_BUF_SIZE 1024

void do_list(int sock, char *dir);
void do_get(char *remote_file, char *local_file);
void do_put(char *filename);

tftpx.h 包含文件

/**********************************************
 * Author: ideawu(www.ideawu.net)
 * Date: 2007-04
 * File: tftpx.h
 *********************************************/

#ifndef TFTPX_H
#define TFTPX_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <dirent.h>


#define CMD_RRQ (short)1
#define CMD_WRQ (short)2
#define CMD_DATA (short)3
#define CMD_ACK (short)4
#define CMD_ERROR (short)5
#define CMD_LIST (short)6
#define CMD_HEAD (short)7


// Without a '/' at the end.
char *conf_document_root;


#define SERVER_PORT 10220
// Max request datagram size
#define MAX_REQUEST_SIZE 1024
// TFTPX_DATA_SIZE
#define DATA_SIZE 512
//
#define LIST_BUF_SIZE (DATA_SIZE * 8)


// Max packet retransmission.
#define PKT_MAX_RXMT 3
// usecond
#define PKT_SND_TIMEOUT 12*1000*1000
#define PKT_RCV_TIMEOUT 3*1000*1000
// usecond
#define PKT_TIME_INTERVAL 5*1000


struct tftpx_packet{
	ushort cmd;
	union{
		ushort code;
		ushort block;
		// For a RRQ and WRQ TFTP packet
		char filename[2];
	};
	char data[DATA_SIZE];
};

struct tftpx_request{
	int size;
	struct sockaddr_in client;
	struct tftpx_packet packet;
};

#endif

/*
Error Codes

   Value     Meaning

   0         Not defined, see error message (if any).
   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.
*/

服务端程序可以从上面链接下载

这里只是介绍客户端代码,但包含的发送和接收机制都是一样的。

谢谢阅读。

猜你喜欢

转载自blog.csdn.net/leon_zeng0/article/details/106883498