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.
*/

服務端程序可以從上面鏈接下載

這裏只是介紹客戶端代碼,但包含的發送和接收機制都是一樣的。

謝謝閱讀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章