FILE TRANSFER PROTOCOL(FTP)意为文件传输协议,用于管理计算机之间的文件传送。FTP 通常指文件传输服务。 FTP 是 Internet 上使用非常广泛的一种通讯协议。它是由支持 Internet 文件传输的各种规则所组成的集合,这些规则使 Internet 用户可以把文件从一个主机拷贝到另一个主机上,因而为用户提供了极大的方便和收益。FTP 和其它 Internet 服务一样,也是采用客户机/服务器方式。使用方法很简单,启动FTP 客户端程序先与远程主机建立连接,然后向远程主机发出传输命令,远程主机在收到命令后就给予响应,并执行正确的命令。FTP 有一个根本的限制,那就是,如果用户未被某一FTP 主机授权,就不能访问该主机,实际上是用户不能远程登录(Remote Login)进入该主机。也就是说,如果用户在某个主机上没有注册获得授权,没有用户名和口令,就不能与该主机进行文件的传输。而 Anonymous FTP(匿名 FTP)则取消了这种限制。FTP 协议与操作系统无关,任何操作系统上的程序只要符合 FTP 协议,均可以互相传送数据。 FTP 应用在 TCP/IP 网络体系结构中位于应用层,所使用的 FTP 协议在网络协议栈中位于 TCP 层之上,属于一种应用层协议, • FTP 应用 FTP 应用采用 C/S 体系结构。客户端程序实现一个命令行或图形界面,将用户命令翻译成 FTP 命令,并发送给服务器端程序。服务器端程序响应 FTP 命令,并将操作成功与否的信息以 FTP 响应形式返回给客户端程序。双方遵守 FTP 协议,完成文件传输服务。 常用的客户端程序有:具有图形界面的 CuteFTP、FlashGet 等软件;命令行界面的 ftp命令(windows 自带)。使用较多的服务器端程序主要有 Serv-U FTP Server、vsftpd 等。 • FTP 常用命令 FTP 常用命令如表 1 所示:
• FTP 响应 FTP 协议规定, FTP 响应以三位数字开始,之后是具体响应信息。FTP 响应第一位数字的意义如下: • 1:肯定预备应答,可以开始数据传输 • 2:肯定完成应答,操作成功完成 • 3:肯定中介应答,等待后续命令 • 4:操作失败,但可以稍后再试 • 5:操作失败,且不要重试 例如,如果向已建立连接的 FTP 服务器发送命令:“SIZE test.txt”,如果当前目录存在 test.txt 文件,则服务器可能会返回“213 122220”,表示操作成功完成,test.txt 文件的大小是 122220 bytes。 • FTP 下载文件流程 FTP 服务端和客户端之间存在两中连接:一中用于传输 FTP 命令(命令必须由客户端主动发起),连接始终存在;另一中用于向客户端传输数据,每当要传输文件或目录文件列表信息时则建立一个数据连接,数据传输完毕立即断开。 数据连接有两种建立方式: PORT 和 PASV。PORT 方式下,客户端监听某端口,服务器主动发起数据连接。PASV 方式下,服务器监听某端口,客户端主动发起数据连接。客户端可分别向服务器发送 PORT 或 PASV 命令指定连接模式。 下载文件之前首先需要登陆,登陆的状态图如图 4 所示。方框表示状态,箭头表示状态变换。箭头上的字符表示客户端向服务器发送的命令或服务器相客户端返回的信息。B 表示开始状态,W 表示等待状态,E 表示出错,S 表示登陆成功。 登陆成功后,发送 TYPE 设定数据传输格式(字符格式还是二进制方式),然后发送PORT 或 PASV 选择数据传输方式。得到成功响应或,根据返回信息与服务器建立数据连接。如果前面发送的命令均得到成功响应,则开始发送下列请求,告诉服务器准备数据。首 先发送 REST 命令(如有需要),指定开始下载的偏移量;然后发送 REST 命令指定要下载的文件。 如果前面发送的命令均得到成功响应,则表示服务器数据准备完毕。下面需要做的是与服务建立数据连接,开始接受数据,并将接收到的数据保存在本地文件中,直到接收完毕后断开数据连接,下载完毕。 FTP 协议本身并没有提供多线程下载的命令,但为实现多线程下载提供了可能。因为REST 命令可以指定偏移量,所以我们可以同时建立多个线程分别从不同部分开始下载,然后将下载的部分重组即可实现多线程下载。在第四部分中将详细介绍。
源代码由标准 C 实现,可以从 ftp://ftp.uk.linux.org/pub/linux/Networking/netkit 上下载。它的注释较为详尽,层次清晰,可读性强。程序中将界面逻辑和业务逻辑分开处理,程序。 源代码共包括 11 个文件,分别是:main.c、glob.c、glob.h、cmds.c、cmds.h、cmdtab.c、ftp.c、ftp_var.h、domacro.c、pathnames.c、ruserpass.c。 其中 glob.c、glob.h、ftp_var.h、cmdtab.c、domacro.c、pathnames.c、ruserpass.c 主要定义一些宏全局变量和全局函数。其中 cmdtab.c 定义了程序支持的所有命令及其相应函数。main.c 实现了程序的主循环,循环等待直到发现用户输入一条命令,然后将命令传递到由 cmds.c、cmds.h 实现的命令行参数处理模块。命令行参数处理模块进行参数性合法性检查,得到合法的参数后将用户请求发送到 ftp.c 实现的用户命令处理模块,最终执行用户请求。(执行过程中大多需要与 FTP 服务器进行连接)。
• 需要解决的问题 需要解决的问题和相应的解决方案为: • 各线程如何共享数据?线程最大的优点是可以方便的共享夫线程的数据,因此各线程可以通过全局变量共享数据。 • 各线程如何保持互斥 ?可通过线程锁来实现对共享变量的互斥访问。访问前加锁,访问完毕解锁。 • 怎样获得文件的大小 ?可以通过向服务器发送“SIZE”命令取得文件的大小。 • 如何将文件平均分给各个进程 ?首先将文件划分为等大小的。 • 各进程如何从指定的位置下载 ? 可以通过向服务器发送“REST”命令指定开始下载的偏移量。 • 如何将不同部分数据合并到同一文件 ?不同线程可打开同一本地文件,通过移动文件指针可实现保存文件的不同部分。 • 增加的全局变量和函数 在原来程序基础上添加的全局变量主要有: loginuser:记录登陆的用户名 password:记录登陆用户使用的密码 downloadfile 记录要下载的文件名 localfile 记录保存的文件名 FileSize 记录文件的大小 宏 MAXTHREADNUM 定义最大允许的线程数 宏 BLOCKSIZE 定义线程下载数据的基本单位,即文件划分得块大小线程锁 lock 用于线程直线互斥访问共享变量 自定义结构 thread_data 的数组变量 threaddata,记录每个线程的数据。 结构 thread_data 定义为 struct thread_data { pthread_t id; /* the ID of the thread */ int number; /* the number of the thread */ int hasbegin; /* indicate the thread has started or not */ int fd; /* the file description */ long start; /* the offset of its part */ long end; /* the end of its thread */ int errornum; /* record the thread's error number */ }; 增加的函数主要有: void mtget(int argc, char *argv[]),主要实现参数合法性检查。 mtrecvfile(const int threadnum, char * local, char * remote ),实现多线程下载功能。 long threadfunction(char * argv),线程运行的函数,完成下载文件某部分的功能。 int ConnectFtp(char* host,int iport),连接服务器 int sendCommand( int sock, char * cmdstr),向服务器发送命令 • 实现步骤 • 在 ftp_var.h 中增加前面提到的全局变量。 • 在 cmdtab.c 中:增加变量 const char mtreceivehelp[] ="receive file byseveral threads";在全局变量 cmdtab 的初始化中增加一行: {"mtget",mtreceivehelp, 1, 1, 1, mtget, NULL, NULL } • 在 cmds.c 中定义函数 mtget,对参数进行合法性检验。 • 在 ftp.c 中定义函数 mtrecvfile,实现多线程下载。主要工作包括:初始化记录各线程状态的变量,检验是否有建立本地文件的权限,获得下载文件的大小,并将任务合理分 配到各个线程中(将分配区间记录在变量 threaddata中),建立一定数目的线程完成下载任务。 • 在 ftp.c 中添加函数 threadfunction,添加完成下载的代码。任务分配算法。假设文件大小为 18KB,线程数为 5,BLOCKSIZE=2048,则分配过程为,首先文件按块大小分为 9 个基本单位,再将 5 个单位平均分配给 5 个线程,即每个线程完成两个基本单位(4KB,最后一个线程例外)。标号为 i 的线程的下载流程为:首先循环等待 threaddata[i-1].start 被置为一,然后继续。这样就保证了线程按顺序启动。下载任务。检查 threaddata[i].start 是否为 1,若为 1 则退出,表示其它线程正在完成本线程的任务;否则继续。通过访问全局变量获得 FTP 服务器 IP 地址和端口号,以及登陆的用户名和密码,登陆服务器。发送命令 TYPE,将数据传送方式设为二进制模式,选择被动模式,并获得服务器的数据端口,发送 REST 指定本进程开始下载的偏移量。打开本地文件,将文件指针移动到相应偏移量。与建立服务器建立数据通道,开始接收数据。下载文本线程数据后,遍历每个进程, 寻找未开始的下载的部分,尝试下载该部分。需要注意的部分:每次向服务器发送请求后,都要检查返回值,判断服务器状态是否正确,否则重试发送。访问敏感的 共享数据时,必须使用互斥锁,否则在本程序中可能会出现同一部分被下载两次的情况。 • 编译与调试 为简化编译过程,重新编写了 MakeFile 文件,增加了-g 的编译选项,从而可使用 gdb调试目标代码;增加了使用多线程库-lpthread 的连接选项,内容如下: EXEC = ftp CC=cc INCLUDES=-I CFLAGS=-g LIBS=-lpthread OBJS = cmds.o cmdtab.o domacro.o ftp.o glob.o main.o ruserpass.o all: $(EXEC) %.o:%.c $(CC) $(CFLAGS) -c $< $(EXEC): $(OBJS) $(CC) $(CFLAGS) $(LIBS) -o $@ $(OBJS) $(LDLIBS) romfs: $(ROMFSINST) /bin/$(EXEC) clean: -rm -f $(EXEC) *.gdb *.elf *.o 调试工具选择了命令行调试工具 gdb。GDB 主要帮忙你完成下面四个方面的功能: 1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。 2、可让被调试的程序在指定的调置的断点处停住。(断点可以是条件表达式) 3、当程序被停住时,可以检查此时你的程序中所发生的事。 4、动态的改变你程序的执行环境。 • 进一步的改进 按照前面设计的多线程实现方式,很容易实现增加断点下载的功能。因为在下载过程中,每个线程均有变量记录当前状态,当发现丢失与服务器的连接或被用户终 止时,将每个线程的状态保存在文件中,当再次下载是,首先从文件中载入断点,启动每个进程从断点出开始下载,直到下载完毕。 [参考文献] • 《 UNIX Network Programming Volume1 3rd.Ed.The Sockets Networking API》Writer:W. Richard Stevens, Bill Fenner, Andrew M. RudoffPublisher:Addison Wesley • RFC959:FILE TRANSFER PROTOCOL(FTP) • 《 Linux 内核分析与实例应用》代玲莉,欧阳劲 编著 国防工业出版社 多线程下载工具 Linuxdown 源代码 孔阳 上海交大 |