首先说结论, 看这个开源博客吧(linux下能运行):GitCode - 开发者的代码家园

网上搜索该资料,有很多知乎或者csdn相关博客,但是很多都是复制来的,代码不全,无法运行。这里给大家推荐这个我找到的开源代码,简单又完整(其它一些开源博客,要么代码量太大太复杂,要么拆成很多个c文件,不便于理解)。此外,该协议原理解释看这个博客: TFTP是什么,一文带你了解-CSDN博客

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#include <setjmp.h>

#define DATA_SIZE   	 512
#define MAX_FILENAME	 255
#define INITIAL_BLOCK 0
#define MAX_TIMEOUTS	 5
#define TIMEOUT_SECS	3

/* Opcodes */
#define RRQ    1
#define WRQ    2
#define DATA   3
#define ACK	   4
#define ERROR  5

static const char *TFTP_error_messages[] = {
	"Undefined error",                 // Error code 0
	"File not found",                  // 1
	"Access violation",                // 2
	"Disk full or allocation error",   // 3
	"Illegal TFTP operation",          // 4
	"Unknown transfer ID",             // 5
	"File already exists",             // 6
	"No such user"                     // 7
};
extern int errno;
typedef struct {
	unsigned short int opcode;
	char filename[MAX_FILENAME];
	char zero_0;
	char mode[MAX_FILENAME];
	char zero_1;
} TFTP_Request;
typedef struct {
	unsigned short int opcode;
	unsigned short int block;
	char data[DATA_SIZE];
} TFTP_Data;
typedef struct {
	unsigned short int opcode;
	unsigned short int block;
} TFTP_Ack;

/* Global vars */
static int is_debugging = 0, timeout;
jmp_buf timeoutbuf, endbuf;

int get_port(char* s) {
	 int retval = 0;
	 char *a = s;
	 while (*a != '\0') {
		  if (*a >= '0' && *a <= '9') {
				retval = retval * 10 + (*a - '0');
		  } else {
				return -1;
		  }
		  a++;
	 }
	 return retval;
}

/* Returns 0 for sucess, otherwise -1. */
int make_socket(struct sockaddr_in *s, char *host, int port) {
	s->sin_family = AF_INET;
	if (host != NULL) {
		struct hostent *he = gethostbyname(host);
		if (he == NULL) {
			perror("gethostbyname");
			return -1;
		}
		s->sin_addr = *((struct in_addr *)he->h_addr);
	} else {
		s->sin_addr.s_addr = htonl(INADDR_ANY);
	}
	s->sin_port = htons(port);
	memset(&(s->sin_zero), 0, 8);
	return 0;
}

/* Returns 0 a file that exists, other -1. */
int file_exists(char *filename) {
	struct stat filebuf;
	if ( stat(filename, &filebuf) == 0 ) {
		return 0;
	} else {
		return -1;
	}
}

/* Fill in the struct using data from buffer */
void packet_to_request(TFTP_Request *r, char *buf) {
	char filename[MAX_FILENAME];
	char mode[MAX_FILENAME];
	short signed int code;
	code = *(short signed int*)buf;
	buf += sizeof(r->opcode);
	strcpy(filename, buf);
	buf += strlen(filename) + 1;
	strcpy(mode, buf);
	r->opcode = ntohs(code);
	strcpy(r->filename, filename);
	r->zero_0 = '\0';
	strcpy(r->mode, mode);
	r->zero_1 = '\0';
}

/* Fill the given buffer using data from the given request */
void request_to_packet(TFTP_Request *r, char *buf) {
	char *pos = buf;
	*(short signed int*)pos = htons(r->opcode);
	pos += sizeof(r->opcode);
	strcpy(pos, r->filename);
	pos += strlen(r->filename) + 1;
	*pos = r->zero_0;
	strcpy(pos, r->mode);
	pos += strlen(r->mode) + 1;
	*pos = r->zero_1;
}

/* Computes length of request */
int request_length(TFTP_Request *r) {
	int len = sizeof(r->opcode) + sizeof(r->zero_0) + sizeof(r->zero_1);
	len += strlen(r->filename) + 1;
	len += strlen(r->mode) + 1;
	return len;
}

/* Returns 0 if request has the given mode, otherwise 0 */
int request_is_mode(TFTP_Request *r, char *mode) {
	if (strcmp(r->mode, mode) == 0) return 0;
	return -1;
}

void request_init(TFTP_Request *r, unsigned short int opcode, char *filename, char *mode) {
	memset(r, 0, sizeof(TFTP_Request));
	r->opcode = opcode;
	strcpy(r->filename, filename);
	r->zero_0 = '\0';
	strcpy(r->mode, "octet");
	r->zero_1 = '\0';
}

void timer(int sig) {
	switch(sig) {
		case SIGALRM: {
			timeout++;
			if (timeout >= MAX_TIMEOUTS) {
				if (is_debugging) printf("Retransmission timed out.\n");
				timeout = 0;
				alarm(0);
				longjmp(endbuf, sig);
			}
			if (is_debugging) printf("Retransmitting.\n");
			longjmp(timeoutbuf, sig);
		} break;
		case SIGINT: {
			if (is_debugging) printf("Transfer interrupted.\n");
			timeout = 0;
			alarm(0);
			longjmp(endbuf, sig);
		} break;
		default: break;
	}
}

void send_data(int sockfd, struct sockaddr *to_addr, socklen_t addr_len, char *filename) {
	int filesize, n, pos = 0, nextAck = -1;
	signed short int block = 0; 
	int fd = open(filename, O_RDONLY, 0006);
	
	if (fd == -1) {
		perror("open");
		exit(1);
	}
	filesize = lseek(fd, 0, SEEK_END);
	lseek(fd, 0, SEEK_SET);
	
	/* Allocate memory for DATA, ACK structs */
	TFTP_Data *dataPacket;
	TFTP_Ack *ack;
	dataPacket = (TFTP_Data *)malloc(sizeof(TFTP_Data));
	ack = (TFTP_Ack *)malloc(sizeof(TFTP_Ack));
	memset(dataPacket, 0, sizeof(TFTP_Ack));
	memset(ack, 0, sizeof(TFTP_Ack));
	
	/* Read from file and send data to server */
	while (pos < filesize) {
		char dataBuf[DATA_SIZE] = {0};
		n = read(fd, &dataBuf, DATA_SIZE);
		if (n == -1) {
			perror("read");
			exit(1);
		}
		pos += n;
		
		block++;
		dataPacket->opcode = htons(DATA);
		dataPacket->block = htons(block);
		if (is_debugging) printf("Block #%d, %d bytes\n", block, n);
		memset(dataPacket->data, 0, DATA_SIZE);
		memcpy(dataPacket->data, dataBuf, n);
		
		sigsetjmp(timeoutbuf, 1); // jump for retransmission
		if (is_debugging) printf("Sending DATA for block #%d\n", block);
		n = sendto(sockfd, dataPacket, 4 + n, 0, to_addr, addr_len);
		if (n < 0) {
			if (is_debugging) printf("Error sending DATA block #%d\n", block);
			break;
		}
		
		if (sigsetjmp(endbuf, 1) != 0) {
			// setup jump for retransmission timeout and interrupt
			break;
		}
		signal(SIGINT, timer);
		signal(SIGALRM, timer);
		
		while (nextAck < 0) {
			/* Receive ACK for the sent DATA */
			if (is_debugging) printf("Waiting for ACK...\n");
			alarm(TIMEOUT_SECS); // start new timer
			n = recvfrom(sockfd, ack, sizeof(TFTP_Ack), 0, to_addr, &addr_len);
			timeout = 0;
			alarm(0);
			if (n < 0) {
				if (is_debugging) printf("Error receiving ACK for block #%d\n", block);
				break;
			}
			
			if (ntohs(ack->opcode) == ERROR) {
				if (is_debugging) printf("Received ERROR: %s\n", TFTP_error_messages[ntohs(ack->block)]);
			}
			else if (ntohs(ack->opcode) == ACK) {
				/* Check block */
				if (ntohs(ack->block) == block) {
					nextAck = 1;
					if (pos == filesize) {
						if (is_debugging) printf("Received ACK for final block #%d\n", block);
						break;
					} else {
						if (is_debugging) printf("Received ACK for block #%d\n", block);
						continue;
					}
				} else if (ntohs(ack->block) < block) {
					nextAck = -1;
				}
			}
		}
		
		nextAck = -1;
	}
	
	free(dataPacket);
	free(ack);
	close(fd);
}

void recv_data(int sockfd, char *filename) {
	struct sockaddr client_addr;
	int n;
	socklen_t clilen;
	short signed int block = 0, nextBlock = 1;

	/* Allocate memory for DATA, ACK and FILE structs */
	TFTP_Data *dataPacket = malloc(sizeof(TFTP_Data));
	TFTP_Ack *ack = malloc(sizeof(TFTP_Ack));
	memset(dataPacket, 0, sizeof(TFTP_Data));
	memset(ack, 0, sizeof(TFTP_Ack));
	FILE * file = fopen(filename, "ab");
	
	while (1) {
		clilen = sizeof(struct sockaddr);
		int length;
		
		if (sigsetjmp(endbuf, 1) != 0) {
			// setup jump for retransmission timeout and interrupt
			break;
		}
		signal(SIGINT, timer);
		signal(SIGALRM, timer);
		
		/* Receive the DATA */
		if (is_debugging) printf("Waiting for DATA...\n");
		alarm(TIMEOUT_SECS); // start new timer
		n = recvfrom(sockfd, dataPacket, sizeof(TFTP_Data), 0, (struct sockaddr *)&client_addr, &clilen);
		timeout = 0;
		alarm(0); // void timer
		if (n < 0) break;
		
		/* Received a duplicate DATA block */
		if (ntohs(dataPacket->block) < nextBlock) {
			if (is_debugging) printf("Received duplicate DATA for block #%d\n", ntohs(dataPacket->block));
		}
		else {
			length = strlen(dataPacket->data);
			if (length == 0) break;
			
			/* Fill the data buffer */
			char dataBuf[DATA_SIZE+1];
			strcpy(dataBuf, dataPacket->data);
			if (length > DATA_SIZE) {
				dataBuf[DATA_SIZE] = '\0';
				length = strlen(dataBuf);
			}
			if (is_debugging) printf("Received DATA for block #%d of size %d\n", ntohs(dataPacket->block), length);

			/* Write data to the file and check if disk is full */
			fwrite(dataBuf, sizeof(char), length, file);
			if (errno == ENOSPC) {
				if (is_debugging) printf("Sending error: %s\n", TFTP_error_messages[3]);
				ack->opcode = htons(ERROR);
				ack->block = htons(3);
				n = sendto(sockfd, ack, sizeof(TFTP_Ack), 0, &client_addr, clilen);
				break;
			}

			/* Fill ACK if correct block received */
			block = nextBlock;
			nextBlock++;
			ack->opcode = htons(ACK);
			ack->block = htons(block);
		}
		
		sigsetjmp(timeoutbuf, 1); // jump for retransmission
		if (is_debugging) printf("Sending ACK for block #%d\n", block);
		n = sendto(sockfd, ack, sizeof(TFTP_Ack), 0, &client_addr, clilen);
		if (n < 0) {
			printf("Error sending ACK.\n");
			break;
		}
		
		/* last block of DATA */
		if (length < DATA_SIZE) break;
	}

	free(dataPacket);
	free(ack);
	fclose(file);
}

void send_error(int sockfd, struct sockaddr *to_addr, socklen_t addrlen, TFTP_Ack *ack, char *msg) {
	int len = sizeof(TFTP_Ack)+strlen(msg)+1;
	char *buf = (char *)malloc(len);
	char *pbuf = buf;
	memset(buf, 0, len);
	*(short signed int*)pbuf = htons(ack->opcode);
	pbuf += sizeof(ack->opcode);
	*(short signed int*)pbuf = htons(ack->block);
	pbuf += sizeof(ack->block);
	strcpy(pbuf, msg);
	sendto(sockfd, buf, len, 0, to_addr, addrlen);
	free(buf);
}

int server_process(int oldsockfd, struct sockaddr *client_addr, TFTP_Request *request) {
	int pid = fork();
	if (pid < 0) {
		perror("fork");
		return -1;
	}
	if (pid == 0) {
		close(oldsockfd);
		
		int sockfd = 0;
		if ((sockfd=socket(AF_INET,SOCK_DGRAM,0)) == -1) {
			perror("socket");
			exit(1);
		}
		struct sockaddr_in serv_addr;
		make_socket(&serv_addr, NULL, 0);
		if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1) {
			perror("bind");
			exit(0);
		}
		unsigned int addr_len = sizeof(struct sockaddr);
		if (getsockname(sockfd, (struct sockaddr *)&serv_addr, &addr_len) == -1) {
			perror("getsockname");
			exit(1);
		}
		if (is_debugging) printf("Child process listening on port %i\n", ntohs(serv_addr.sin_port));
		
		/* Allocate memory for ACK struct */
		TFTP_Ack *ack = (TFTP_Ack *)malloc(sizeof(TFTP_Ack));
		memset(ack, 0, sizeof(TFTP_Ack));
		
		/* Octet mode only */
		if (request_is_mode(request, "octet") != 0) {
			ack->opcode = htons(ERROR);
			ack->block = htons(0);
			send_error(sockfd, client_addr, addr_len, ack, "mode not supported");
			free(ack);
			close(sockfd);
			free(request);
			exit(1);
		}
		
		if (request->opcode == WRQ) {
			/* file should not exist locally */
			if ( file_exists(request->filename) == 0 ) {
				exit(1);
			}
			
			/* Fill ACK struct and compute packet length */
			ack->opcode = htons(ACK);
			ack->block = htons(INITIAL_BLOCK);
			/* Send ACK and start receiving DATA */
			sendto(sockfd, ack, sizeof(TFTP_Ack), 0, client_addr, addr_len);
			recv_data(sockfd, request->filename);
		}
		else if (request->opcode == RRQ) {
			/* file must exist locally */
			if ( file_exists(request->filename) == -1 ) {
				exit(1);
			}
			/* Send data */
			send_data(sockfd, client_addr, addr_len, request->filename);
		}
		else {
			if (is_debugging) printf("Received request with unknown opcode: %i\n", request->opcode);
		}
		
		free(ack);
		close(sockfd);
		free(request);
		exit(0);
	}
	return pid;
}

void chld_trap(int s) {
	int pid = wait(NULL);
	if (pid < 0) {
		return; // wait went wrong, fly away
	} 
}

void run_server(int port) {
	/* install signal handlers */
	signal(SIGCHLD, chld_trap);
	
	int sockfd;
	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	unsigned int addr_len, numbytes;
	char *buf;
	
	/* Try to bind socket to the port */
	if ((sockfd=socket(AF_INET,SOCK_DGRAM,0)) == -1) {
		perror("socket");
		exit(1);
	}
	make_socket(&my_addr, NULL, port);
	if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
		perror("bind");
		exit(1);
	}
	
	/* Listen for incoming requests until program is teminated */
	if (is_debugging) printf("Listening on port %i\n", ntohs(my_addr.sin_port));
	while (1) {
		buf = (char *)malloc( sizeof(TFTP_Request) );
		memset(buf, 0, sizeof(TFTP_Request));
		addr_len = sizeof(struct sockaddr);
		
		numbytes = recvfrom(sockfd, buf, sizeof(TFTP_Request), 0, (struct sockaddr *)&client_addr, &addr_len);
		if (numbytes < 0) {
			free(buf);
			continue;
		}
		
		if (is_debugging) {
			printf("Got packet from %s:%d, %i bytes.\n",
						inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), numbytes
			);
		}
		
		TFTP_Request *request = (TFTP_Request *)malloc( sizeof(TFTP_Request) );
		packet_to_request(request, buf);
		free(buf);
		
		server_process(sockfd, (struct sockaddr *)&client_addr, request);
		free(request);
	}
	
	close(sockfd);
}

void send_request_read(char *host, int port, char *filename) {
	int n, len;
	int mysockfd;
	struct sockaddr_in serv_addr;
	struct sockaddr_in my_addr;
	unsigned int addr_len;
	
	/* Prepare the sockets */
	make_socket(&serv_addr, host, port);
	if ((mysockfd=socket(AF_INET,SOCK_DGRAM,0)) == -1) {
		perror("socket");
		exit(1);
	}
	make_socket(&my_addr, NULL, 0);
	if (bind(mysockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
		perror("bind");
		exit(1);
	}
	addr_len = sizeof(struct sockaddr);
	
	/* Allocate the Ack struct */
	TFTP_Ack *ack =  (TFTP_Ack *)malloc(sizeof(TFTP_Ack));
	memset(ack, 0, sizeof(TFTP_Ack));
	
	/* Create the request */
	TFTP_Request *request = (TFTP_Request *)malloc(sizeof(TFTP_Request));
	request_init(request, RRQ, filename, "octet");
	len = request_length(request);
	char *req_buffer = (char*)malloc(len);
	memset(req_buffer, 0, len);
	request_to_packet(request, req_buffer);
	
	/* Send the request */
	sigsetjmp(timeoutbuf, 1);
	if (is_debugging) printf("Sending read request to server, %i bytes.\n", len);
	n = sendto(mysockfd, req_buffer, len, 0, (struct sockaddr *)&serv_addr, addr_len);
	if (n < 0) {
		if (is_debugging) printf("Error sending read request!\n");
		free(ack);
		free(request);
		free(req_buffer);
		close(mysockfd);
		exit(1);
	}
	
	/* Just receive data */
	recv_data(mysockfd, filename);
	
	free(ack);
	free(request);
	free(req_buffer);
	close(mysockfd);
}

void send_request_write(char *host, int port, char *filename) {
	int mysockfd, n, len;
	struct sockaddr_in serv_addr, my_addr;
	unsigned int addr_len;
	
	/* Prepare the sockets */
	make_socket(&serv_addr, host, port);
	if ((mysockfd=socket(AF_INET,SOCK_DGRAM,0)) == -1) {
		perror("socket");
		exit(1);
	}
	make_socket(&my_addr, NULL, 0);
	if (bind(mysockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
		perror("bind");
		exit(1);
	}
	addr_len = sizeof(struct sockaddr);
	
	/* Allocate the Ack struct */
	TFTP_Ack *ack =  (TFTP_Ack *)malloc(sizeof(TFTP_Ack));
	memset(ack, 0, sizeof(TFTP_Ack));
	
	/* Create the request */
	TFTP_Request *request = (TFTP_Request *)malloc(sizeof(TFTP_Request));
	request_init(request, WRQ, filename, "octet");
	len = request_length(request);
	char *req_buffer = (char*)malloc(len);
	memset(req_buffer, 0, len);
	request_to_packet(request, req_buffer);
	
	/* Send the request */
	sigsetjmp(timeoutbuf, 1); // jump for retransmission
	if (is_debugging) printf("Sending write request to server, %i bytes.\n", len);
	n = sendto(mysockfd, req_buffer, len, 0, (struct sockaddr *)&serv_addr, addr_len);
	if (n < 0) {
		if (is_debugging) printf("Error sending write request!\n");
		exit(1);
	}
	
	if (sigsetjmp(endbuf, 1) != 0) {
		// jump for retransmission failure or interrupt
		free(ack);
		free(request);
		free(req_buffer);
		close(mysockfd);
		exit(1);
	}
	
	/* Wait for the Ack */
	if (getsockname(mysockfd, (struct sockaddr *)&my_addr, &addr_len) == -1) {
		if (is_debugging) perror("getsockname");
		exit(1);
	}
	if (is_debugging) printf("Waiting for ACK on port %d...\n", ntohs( my_addr.sin_port ));
	signal(SIGINT, timer);
	signal(SIGALRM, timer);
	alarm(TIMEOUT_SECS);
	n = recvfrom(mysockfd, ack, sizeof(TFTP_Ack), 0, (struct sockaddr *)&serv_addr, &addr_len);
	alarm(0);
	timeout = 0;
	if (n < 0) {
		if (is_debugging) printf("Error receiving ACK from server!\n");
		exit(1);
	}
	
	/* Check the Opcode */
	if (ntohs(ack->opcode) == ERROR) {
		if (is_debugging) printf("Received ERROR: %s\n", TFTP_error_messages[ntohs(ack->block)]);
	}
	else if ( ntohs(ack->opcode) == ACK && ntohs(ack->block) == INITIAL_BLOCK ) {
		if (is_debugging) printf("Received ACK for block #%d\n", ntohs(ack->block));
		send_data(mysockfd, (struct sockaddr *)&serv_addr, addr_len, filename);
	} else {
		if (is_debugging) printf("Received packet other than expected ACK\n");
	}
	
	free(ack);
	free(request);
	free(req_buffer);
	close(mysockfd);
}

void run_client(char *host, int port, char mode, char *filename) {
	void (* sender)(char *, int , char *) = NULL;
	int exist = (mode == 'r') ? 0 : -1;
	if (file_exists(filename) == exist) {
		/* Nothing to do here */
		exit(1);
	}
	sender = (mode == 'r') ? &send_request_read : &send_request_write;
	if (sender != NULL) sender(host, port, filename);
}

/*
 * Server: mytftp -l [-p port] [-v]
 * Client: mytftp [-p port] [-v] [-r|w file] host
 */
int main(int argc, char** argv) {
	int server = 0;
	int port = 3335;
	char mode = 0;
	char *filename = NULL;
	char *host = NULL;
	
	/* Parse the arguments */
	while (--argc > 0) {
		char *str = *++argv;
		if (*str != '-') {
			host = (char *)malloc( sizeof(char)*(strlen(str)+1) );
			strcpy(host, str);
			continue;
		}
		str++;
		if (*str == 'l') {
			server = 1;
		}
		else if (*str == 'p') {
			if (--argc > 0) {
				port = get_port(*++argv);
				if (port < 0) {
					printf("Invalid port number: %s\n", *argv);
					exit(0);
				}
			}
		}
		else if (*str == 'v') {
			is_debugging = 1;
			printf("Verbose mode on.\n");
		}
		else if (*str == 'r' || *str == 'w') {
			mode = *str;
			--argc;
			filename = (char *)malloc( sizeof(char)*(strlen(*++argv)+1) );
			strcpy(filename, *argv);
		}
	}
	
	if (server == 1) {
		run_server(port);
	} else {
		if (host != NULL && mode > 0 && filename != NULL) {
			run_client(host, port, mode, filename);
			free(host);
			free(filename);
		} else {
			printf("Usage:\nServer: mytftp -l [-p port] [-v]\nClient: mytftp [-p port] [-v] [-r|w file] host\n");
		}
	}

	return 0;
}

更多推荐