我先研究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.
*/
服务端程序可以从上面链接下载。
这里只是介绍客户端代码,但包含的发送和接收机制都是一样的。
谢谢阅读。