自己写一个FTP客户端程序的过程

前言

以前在一个项目中遇到了内外网分离的问题,内网和外网不能直接通信,项目中外网的机器需要从内网拿数据,因此中间配置了一台FTP服务器,内网产生的数据形成文件,然后传到中间FTP服务器上,外网定时去FTP服务器上取文件。你也许会问,外网怎么知道内网产生了哪些文件呢?真是一个好问题,这说明你反映很快,一下子就融入到应用场景中去了。停下来想一下,如果是你会怎么做呢?

用一个简单的方法吧,设置一个中间文件,内网把产生的文件信息(主要是文件名)追加到一个固定的文件中,然后传到FTP服务器上,外网机器先去FTP服务器取到中间文件,与上次最后取的文件信息比较,判断有没有新文件生成,如果有新文件,就通过FTP协议取下来,如果没有就等下次时间间隔到了,再如上判断。

这里还有一个问题,外网机器怎样取文件呢?首先会想到,可以用一个第三方的FTP客户端程序,写一个脚本定时启动获取文件,如果这样做了,那你就不会看到这篇文字了。为了显得更专业一些,也为了程序集成在一起好控制,项目组决定自己写程序获取文件。

怎样实现呢

如果自己写程序,就遇到了一个问题,要自己写代码实现FTP协议,乍一看好像很简单,不就是实现几个FTP命令吗?当然最快速的方法就是去问一下其他人,看是否有人做过这方面的东西,也好参考一下,问了一圈项目组里的同事,没一个人做过。你们让我做的时候,不是都说很简单的东西吗,怎么就没人研究过呢?看来空闲时间都用来玩游戏了,也不干点有用的事情。抱怨是不能解决问题的,只能自己先抓只螃蟹来吃了。

困难可以激发斗志,先从FTP协议开始研究,下载了RFC959文件,看了一遍,还是一头雾水,可能很多人跟我一样,读这样的英文文档有些吃力,已经来不及补英文了,暗自懊恼,以前的时间都干嘛了,怎么就没好好用功把英语学好呢。看来读懂协议再写程序,这条路太耗时,项目还等着用呢,也不能让其他人看了笑话。

到了考验你解决问题能力的时候了,不能慌也不能急,没吃过猪肉,还没见过猪跑吗?不是也经常用FTP传文件吗?先来考察它都干了些什么?

首先要有一个IP地址,这个没有问题,毕竟FTP是基于TCP/IP协议的,端口默认是21,从以前对FTP的了解知道还要用到一个端口20,用于传送数据,到底什么时候用到20端口,这个还不清楚,以后再说吧。然后要通过TCP/IP协议连接到上面的IP地址和21端口,接着要输入用户名和密码来验证授权,后面就是设置通信模式,再后面就是输入命令,传送文件了。

先把流程搞清楚,不是有现成的FTP服务器程序和客户端程序吗?好吧,自己先写一个假的服务器程序,只接收客户端的命令,看看客户端都做了些什么动作,然后按顺序记录下来。接着写一个客户端程序,按照假的服务器程序接收到的信息,按步骤发送给已有的FTP服务器程序,看看服务器发送回来什么样的应答,这样就把开始的交互流程理清楚了。

说到传输数据,从协议中看到,FTP传送数据有两种模式主动模式,也叫端口模式(port)和被动模式(passive)。这里的主动和被动是相对于FTP服务器来说的,这两种模式分别对应两种场景,尤其是在机器中部署了防火墙软件时。下面分别来描述这两种情况。

主动模式是服务器端主动去连接客户端的监听建立连接,然后传输数据。FTP有两条链路在工作,一条是控制链路,这条在21端口,一条是数据链路,就是我们现在要描述的传输数据的通道。客户端程序连接服务器的21端口,建立了控制链路,以后所有的控制命令都通过这条连接交互(后面我们会区分哪些是控制命令,哪些是数据命令)。连接成功后我们在客户端就得到了一个随机分配的端口(如果不明白socket的五元组,可以看后面附录的解释),我们把这个端口叫做N,然后我们把这个端口加1,就是N+1端口,在这个端口上用客户端的IP地址建立一个监听,然后使用PORT命令把客户端监听的IP和端口N+1通过控制链路发送给FTP服务器。服务器接到PORT命令,就知道客户端要求传输数据了,于是就解析出客户端的IP地址和端口,然后连接到客户端的监听,这样就建立了一条数据链路,后面的数据传输就可以通过这条链路进行了。假设后面客户端输入了一个GET命令(对应着FTP中的RETR <filename>),那么服务器就打开这个文件,读入数据,通过数据链路发送给客户端,经过多次交互,传输文件的动作就完成了。上面是一个连贯的过程,但中间还有一个小插曲,现在让我们回过头来看一下,在服务器端主动连接客户端监听的时候,服务器还要做一个动作,就是要指定自己的端口为20,而不是让系统随机分配一个,在写程序的时候可以通过bind()函数来指定20端口,现在我们明白为什么FTP要预留21和20两个端口了。

被动模式是服务器建立监听,客户端去连接来建立数据链路的方式。客户端连接FTP服务器建立了控制链路后,得到了一个本地的随机分配端口N,再加1又得到一个端口N+1,这个端口在客户端连接服务器建立数据链路时,指定为客户端的数据端口。随后客户端通过控制链路给服务器发送一个PASV,告诉服务器要采用被动模式,服务器以一个应答码227作为回应,后面跟着服务器指定的连接信息,就是IP地址和端口,格式是(h1,h2,h3,h4,p1,p2)。客户端解析出IP地址和端口后,连接到服务器建立数据链路(别忘了连接前要绑定N+1端口)。然后客户端就可以发布数据命令传输数据了。

下面看看FTP命令都有哪些,这些是FTP协议中列出的命令。

ABOR

异常中断数据连接

ACCT

账户权限信息

ALLO

为服务器上的文件存储分配空间

APPE

在服务器文件中追加数据

CDUP

改变服务器上当前目录到上一级目录

CWD

改变服务器当前目录到一个新目录

DELE

删除服务器上的文件

HELP

返回命令的帮助信息

LIST

显示服务器上的文件或目录

MODE

改变传输模式

MKD

在服务器上建立目录

NLST

列出服务器上的文件或子目录(不带属性)

NOOP

空指令

PASS

提供用户密码

PASV

设置被动模式

PORT

设置主动模式

PWD

显示服务器上的当前工作目录

QUIT

退出登录

REIN

重新初始化

REST

从指定的偏移量重启文件传送

RETR

从服务器传送文件到客户端

RMD

删除服务器上的目录

RNFR

指定需要重命名的文件

RNTO

指定重命名文件的新名称

SITE

提供服务器相关参数

SMNT

安装文件系统

STAT

返回文件或目录的状态信息

STOR

从客户端传送文件到服务器上

STOU

从客户端传送文件到服务器,不覆盖同名文件

STRU

指定文件结构

SYST

返回服务器的系统信息

TYPE

指定文件类型

USER

指定登录用户名

FTP命令都以ASCII码形式发送,命令以CR、LF结尾,也就是以回车+换行表示一个完整命令,大部分的命令后面会带着参数,参数和命令之间用空格分开。这里大部分是控制命令,不需要建立数据链路。传输数据的命令有文件操作的命令,上传和下载文件(如STOR,STOU,RETR),还有LIST和NLST命令。详细的命令参数,参照一下FTP协议好了,这里不一一列出了。

还有一个问题是要了解服务器端返回的响应码格式,FTP的响应码是三个数字,第一个数字代表响应的好坏,第二个数字代表响应的详细信息,第三位预留做其他信息。第一个数字是1到5这些值,1表示在下一命令前等待应答;2表示完成应答,操作已完成,可以进行新命令;3表示命令已接受,但需要提供更多信息;4表示暂时拒绝应答,临时错误,可在以后再次发送命令;5表示永远拒绝应答。抄一个应答表放在下面作为参考。

110

重新启动标记应答。在这种情况下文本是确定的,它必须是:MARK yyyy=mmmm,其中yyyy是用户进程数据流标记,mmmm是服务器标记。

120

服务在nnn分钟内准备好

125

数据连接已打开,准备传送

150

文件状态良好,打开数据连接

200

命令成功

202

命令未实现

211

系统状态或系统帮助响应

212

目录状态

213

文件状态

214

帮助信息,信息仅对人类用户有用

215

名字系统类型

220

对新用户服务准备好

221

服务关闭控制连接,可以退出登录

225

数据连接打开,无传输正在进行

226

关闭数据连接,请求的文件操作成功

227

进入被动模式

230

用户登录

250

请求的文件操作完成

257

创建"PATHNAME"

331

用户名正确,需要口令

332

登录时需要帐户信息

350

请求的文件操作需要进一步命令

421

不能提供服务,关闭控制连接

425

不能打开数据连接

426

关闭连接,中止传输

450

请求的文件操作未执行

451

中止请求的操作:有本地错误

452

未执行请求的操作:系统存储空间不足

500

格式错误,命令不可识别

501

参数语法错误

502

命令未实现

503

命令顺序错误

504

此参数下的命令功能未实现

530

未登录

532

存储文件需要帐户信息

550

未执行请求的操作

551

请求操作中止:页类型未知

552

请求的文件操作中止,存储分配溢出

553

未执行请求的操作:文件名不合法

主要的流程设计一下

我们这个FTP客户端程序是在Linux下用C语言开发的,所以我们的设计也用C语言的格式来描述,这样更清晰一些。

我们先来看一下主函数的流程。

int main(int argc, char **argv)

{

  /* 解析命令行输入参数 */

  parse_arguments(argc, argv);

  /* 连接到FTP服务器,完成登录 */

  ftp_open();

  /* 得到N+1 */

  get_host_port();

  /* 进入命令循环 */

  While (loop)

  {

    /* 显示FTP提示符 */

    fprintf(stderr, "ftp> ");

    /* 等待输入命令到命令缓冲区 cmdbuf */

    /* 去掉命令中开始的空格和结尾的回车换行符 */

   

    /* 分解出命令和命令参数 */

    /* 在命令列表中查找已定义的命令,如果找到,通过相连的

     * 命令处理函数执行命令动作

     */

  }

  /* 关闭与FTP服务器的连接,退出程序 */

}

接下来我们看一下取文件的处理流程,这里用到了主动和被动两种模式。

int ftp_get(int sd, char *args)

{

  /* 从参数args中取到文件名,如果没有指定目标文件名,以源文件名作为目标文件名 */

  /* 准备好取文件的控制命令 RETR filename CR LF */

  if (g_flag & FTP_FLAG_PASSIVE_ON)

  {

    /* 被动模式 */

    /* 发送被动模式命令 PASV CR LF */

    /* 接收应答,如果不是227,说明有错误 */

    /* 解析227 后面的应答参数h1,h2,h3,h4,p1,p2

     * 得到要连接的主机监听的端口

     */

    /* 上面的这些流程在ftp_passive()中实现 */

    /* 发送获取文件的控制命令 */

    /* 连接到FTP服务器监听端口,建立数据链路 */

    /* 接收控制命令应答 */

  }

  else

  {

    /* 主动模式 */

    /* 建立一个INET流模式的socket

     * 绑定到本地控制连接端口N+1的端口

     * 调用listen()监听

     */

    /* 准备PORT命令,把客户端IP地址和N+1端口

     * 按照h1,h2,h3,h4,p1,p2的格式拼成参数

     */

    /* 发送PORT控制命令 */

    /* 上面这些流程在create_listener()中实现 */

    /* 发送获取文件的控制命令 */

    /* 接收FTP服务器的连接,建立数据链路 */

    /* 接收控制命令的应答 */

  }

  /* 打开本地目标文件 */

  /* 循环读取服务器端发送的文件数据,写入本地目标文件 */

  while (1)

  {

    /* 调用recv_data()从数据链路上读取数据 */

    /* 如果全部数据读取完毕(rc == 0),退出循环 */

    /* 把读到的数据写入到本地文件中 */

  }

  /* 关闭本地文件 */

  /* 关闭数据链路 */

  /* 如果是主动模式,关闭本地的监听连接 */

  /* 接收文件传输完毕的控制应答 */

}

其他的处理流程都是根据命令,写一个函数来处理,这里就不一一来描述了,源代码的逻辑也很简单,很容易看懂。

后语

你也许能看出来,这个文档是后写的。当初在项目中,也没有时间写这些东西,只是先把功能实现了,跑起来再说。现在把当时的代码拿来,改造成一个命令行的程序,等于把当时的情景和程序都重温了一遍,把代码也修改得更容易读一些。现在想一想,一些看似简单的东西,如果不去亲自做一下,也还是很有挑战的,当时项目组中的其他人不愿意去做,也可能觉得看起来简单,实际做起来也许不那么容易,做不出来,还很没面子。但是,程序员不应该惧怕挑战,只有克服了困难,才能提高,只有在程序跑通的刹那间,才能体会那种特有的喜悦。

这段时间想把以前做过的一些东西总结一下,有时候觉得很多东西在脑子里都是一盘散沙,以为当初工作中学到的东西都比较急迫,基本要求现学现用,所以有些也理解的不透彻,还有一些事完全没搞懂,只是工作应付过去了,也就放下了。后来想通过自己定义几个虚拟的项目,把一些东西写成文字,也通过写程序去实现它,做的过程中才发现好多东西也不确定,又重新查找资料,等于又重新学习了一遍。这样感觉也很好,孔子曰:温故而知新,很有道理,原来一些似是而非的地方总算搞清楚了。

总结的东西写成了文档,通过源代码实现了功能,觉得总算踏实了些,如果你也想总结一下自己过去的工作,不妨也这么来做。我注册了一个网站,把这些东西放到了上面,如果你想看一下,请访问:http://www.tomcoding.com

源代码可在www.tomcoding.com网站下载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值