我先研究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.
*/
服務端程序可以從上面鏈接下載。
這裏只是介紹客戶端代碼,但包含的發送和接收機制都是一樣的。
謝謝閱讀。